You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An @Name / @First.Last mention of an agent never reaches it. Fuzzy mentions resolve only against the pingable set (GET /circles/people.json), and an agent can never be pinged by design, so the name always misses. The line posts as plain text with an Unresolved mentions left as text notice and exit 0. [@Name](person:ID) with an agent's ID goes through the same pingable set and fails the whole command with Person not found.
Fuzzy @Name: when the pingable set has no exact answer and the command has a project in scope, the agents on that project are candidates too. They come from GET /projects/:id/people.json, which lists agents and includes the attachable_sgid a mention needs. The project comes from, in order: the project the command already resolved (messages create, cards create, schedule create/update, todos sweep, chat post without --room), then the item URL's bucket, then --in. The configured default project is not used for a bare ID, because it says nothing about where that item is. A comments create batch posts exactly as before, and its agent scope is only a project every target is known to be in: the shared URL project, --in for bare IDs, or both when they agree. When its URLs name different projects it gets no scope. A project that can't be resolved when an agent lookup needs it fails the command and is not treated as an unresolved mention.
Precedence: a name equal to an agent's name (ignoring case) beats a person whose name only contains it, so @Quincy reaches the agent Quincy and not a pingable "Quincy Jones". In every other case people keep precedence, and an agent matches on a partial name only when no person matches at all. Ambiguity is still reported, and resolves to unresolved text as before.
People semantics are unchanged: only rows with personable_type: "Agent" are taken from the project list, so a person who is not pingable still can't be mentioned.
[@Name](person:ID): on a pingable miss, one GET /people/:id call, accepted only when the person is an agent. It needs no project. A failure of the pingable fetch still fails the command and is not read as a miss.
Cost: nothing extra when the pingable set has an exact match; the project is resolved lazily. Otherwise there is one project-people fetch per project per run, cached on the resolver. If that fetch fails while the pingable set already had a partial match, the person match is kept. If agents were the only possible answer, the error fails the command, the same as a pingable failure does today.
The miss is not silent, and wasn't before: every call site already sends the notice through WithDiagnostic, which puts it in the JSON notice and on stderr in --agent/--quiet mode (TestChatPostAgentModeWarningOnStderr). The notice now also says how an agent can be reached: (agents match by name only among the people on the command's project; name it with --in or a URL if the command has none; [@Name](person:ID) needs no project).
The basecamp skill's mention section now describes agent resolution.
messages.go gets the same three-line change as the other call sites (a scope argument, plus the URL's bucket for messages update), and nothing else in that file changes.
Test
internal/names/mention_test.go runs a fake server where the agent is in the project's people and not in the pingable set. It checks: resolution by name with a single fetch per list; that a pingable hit makes no project call and never resolves the scope; that a non-pingable person in the project stays unmentionable; that nothing resolves without a scope; exact-agent-over-partial-person; agent ambiguity; that project and pingable API failures stay hard while a failed agent lookup keeps a person match; and agent resolution by person ID.
internal/commands/mention_agents_test.go runs comments create with a card URL, with --in, and with a bare ID (no project, so the notice with the hint appears), plus [@Quincy](person:7) and chat post --room --in. Each one failed on main (plain text, or Person not found: 7).
The chat mention mock now serves the project's people list, which the fallback reads.
make check: the same results as a clean origin/main run on this machine, apart from the new tests. The failures on both are TTY/environment tests (wizard first-run, connect doctor, delete-confirmable, bridge token).
Adds project-aware agent mention resolution while preserving existing pingable-person behavior.
Changes:
Resolves fuzzy agent names from project membership and agent IDs directly.
Propagates project scope through mention-capable commands.
Adds documentation and coverage for resolution, caching, errors, and diagnostics.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Correct agent-scope guidance for create commands and chat post
internal/commands/helpers.go:757
This notice says --in or a URL are the only sources of agent scope, but create commands and chat post without --room also pass their already-resolved project, including a configured default or interactive selection. That makes the user-facing explanation inaccurate on misses in those commands; describe the command project generally and present --in/URL as ways to provide it.
Document resolved project as the agent scope
skills/basecamp/SKILL.md:98
This description omits commands that already resolve a project from configured/default context (for example, message/card/schedule creation and chat post without --room), even though those call sites pass that resolved project as the agent scope. Clarify that the command’s resolved project is used first; otherwise agents using the skill may unnecessarily add --in or assume the mention cannot resolve.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
Mixed comment batches can resolve agents from a project that does not contain every target.
Review effort: Balanced Findings: None
Previously missed (1)
In code that hasn't changed since last review
Prevent mixed batches from using an unrelated explicit project scope
internal/commands/helpers.go:746
A mixed batch can still select an agent from a project that does not hold every target. For example, <URL in project 123>,<bare ID> --in 456 clears the known URL project and returns scope 456 (the new test even expects this), so the project-123 comment receives a mention resolved from project 456. Preserve the known URL project and require the explicit scope to agree with it; otherwise return no batch scope (or a clear conflict error).
Status at 7ef9724: CI is green. Copilot and Codex have both reviewed this exact commit (Codex found no issues). All 9 review threads are fixed and resolved.
Fixed from the review threads:
Batch scope: a bare ID in a batch no longer takes the project from the URL targets, and URLs that name different projects get no scope.
A project that can't be resolved fails the command; it is never treated as an unresolved mention.
A failed project-people fetch is remembered for the run, so it is not retried.
chat update and schedule update take the mention scope from the URL's project first.
The unresolved-mention notice and the skill docs now say the command's own project comes first.
One item needs a human decision. Copilot's latest review body lists it under "Previously missed", with no thread: comments create <URL in project 123>,<bare ID> --in 456 scopes the batch to 456, so the comment on the project-123 item could carry a mention resolved from project 456. This is the third review finding about how a comments create batch picks its mention scope, so I stopped here instead of adding a third patch. The options:
(a) Use --in only when it matches every URL in the batch. That is Copilot's suggestion.
(b) Ignore --in for any batch that contains a URL.
(c) Resolve mentions separately for each target. This needs a different code structure.
Option (a) is a few lines. It needs someone to decide whether it belongs in this PR.
Fixed in e80ab94: Copilot's "Previously missed" finding from its review body (<URL in 123>,<bare ID> --in 456 scoping the batch to 456).
We chose the strict rule. A comments create batch that has --in and any URL target in a different project is now refused with a usage error, before anything is posted. For example: --in names project 456, but a URL target is in project 123, with the hint "Drop --in to let each URL name its project, or split the batch by project". When every URL's project matches --in, or the batch has no URL, the command behaves as before. Mentions are not resolved separately for each target.
The rule covers every such batch, not only ones with mentions. Before this change, --in was silently ignored for URL targets. That was conflicting input, and the command now reports it.
comments create is the only command that uses this batch-scoping helper, so no other command changes. Tests: TestCommentsCreateRefusesURLProjectConflictingWithIn (it failed before this change), TestCommentsCreateAcceptsURLProjectMatchingIn, and TestBatchProject.
Handle duplicate exact agent names instead of selecting first match
internal/names/mention.go:64
resolve returns the first case-sensitive exact match immediately, so if the project contains two agents with the same display name, this path silently mentions whichever row arrived first instead of reporting ambiguity. Check for multiple exact agent-name matches before calling resolve, preserving the existing ambiguity behavior rather than selecting an arbitrary agent.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
Batch comments eagerly resolve named projects, defeating lazy lookup and introducing avoidable failures.
Review effort: Balanced Findings: None
Previously missed (1)
In code that hasn't changed since last review
Defer named --in resolution until fuzzy agent fallback
internal/commands/helpers.go:764
This eagerly resolves a named --in whenever the batch contains a URL, before resolveMentions knows whether project scope is needed. Consequently even plain content—or an exact pingable mention that should cost no project lookup—now fetches /projects.json and can fail during a project-list outage. Please defer this name/conflict resolution into the ProjectScope callback so it runs only when fuzzy resolution actually falls back to agents.
Status at cc8c2a7: CI is green (25 pass, 5 skipped) and the branch merges cleanly. Copilot and Codex have both reviewed cc8c2a7. Codex found no issues; Copilot's body lists one earlier finding, declined below.
Fixed since the last summary:
--in and URL project ids are compared as numbers, so --in 00123 matches bucket 123 (thread).
Two agents on the project with the same name, ignoring case, are now reported as ambiguous instead of the first one being picked (from Copilot's review body).
An agent looked up by person ID is cached for the run, so repeated [@Name](person:ID) mentions make one request.
Declined:
Resolving a named --in up front for URL batches (the Codex thread, and Copilot's review body on 3 rounds). Refusing contradictory input up front is the intended rule. The extra lookup only happens when --in is a project name and the batch also has URL targets. A bad name failing is correct, because it's the user's own input. A lazy check would let contradictory input post with no error.
Schedule-update mention scope should follow the command's resolved project (Copilot's review body). The entry being updated is the one the URL names, so the URL's project is the one whose agents can see it. Taking the scope from the URL first is deliberate, and it is what an earlier Codex finding asked for.
Open, needs a human decision (thread): a malformed token in a batch (<URL in 123>,garbage) counts as a bare ID. That clears the URL-derived project, so an agent mention is left as text even though the only item actually commented on is in 123. This is another case of how a batch picks its mention project, so it was reported rather than patched. The fix would be small: count only numeric bare IDs.
Fuzzy @name mentions resolve only against the pingable set, and agents
are never pingable by design, so an @mention meant for an agent always
posted as plain text. When the pingable set has no exact answer and the
command has a project in scope (a project it resolved, the item URL's
bucket, or --in), the agents on that project's people list are
candidates too. A name equal to an agent's wins over a person whose name
merely contains it; people otherwise keep precedence, ambiguity is still
reported, and a person who is not pingable is still not mentionable.
[@name](person:ID) for an agent's ID now resolves through a person
lookup instead of failing the command. The unresolved-mention notice
says how an agent can be reached.
A bare ID in a comment batch leaves the batch's project unknown, so the
URL targets' bucket no longer scopes it. A project that cannot be
resolved fails the command instead of reading as an unresolved mention.
A failed agent fetch is remembered for the run. Schedule and chat
updates take their scope from the URL, then the project they resolved.
…et no scope
A project that cannot be resolved now fails the mention even when a
pingable person partially matches. A comment batch whose URLs name
different projects gets no mention scope, not --in, since one resolved
agent cannot be right for both.
One mention is resolved for every target in a comments create batch, so
the batch needs one project. When --in names a project and any URL target
is in a different one, the command now fails with a usage error naming
both projects, before anything is posted. When every URL agrees with
--in, or there is no URL, nothing changes.
… batch
The strict rule refused a batch whose URL project differed from --in even
when nothing in the body needed a project, changing which comments post
for a reason unrelated to mentions. The batch now posts exactly as before,
with a URL's bucket winning over --in, and only the agent mention scope
depends on the targets: a project every target is known to be in, or none.
A named --in is resolved only when an agent lookup consults the scope, and
tokens the posting loop skips no longer unscope the batch.
Status at 67d6531: rebased onto main, which now includes #783, #784, #785 and #799; the rebase had no conflicts. CI is green (25 pass, 5 skipped) and the branch merges cleanly. Copilot approved this head and Codex found no issues. There are no unresolved threads. A local make check shows the same failures as a clean origin/main on this Mac (TTY/environment tests, the claude driver's flaky cancel tests, and the mcp.go/fcntl lint findings).
Change of direction: the strict refusal is gone. I reconsidered the rule that refused a comments create batch whose URL project differed from --in. It refused the batch even when no mention needed a project. So it changed which comments post, for a reason that has nothing to do with mentions. It also produced most of the late review threads.
It needed a project lookup before anything else ran, which Copilot flagged three times and Codex once.
It had edge cases: numeric id comparison, and malformed tokens.
Now the batch posts exactly as it did before this PR, with a URL's project winning over --in. Only the agent mention scope depends on the targets, and it is always a project every target is known to be in:
Targets
Agent scope
All URLs, one project
that project
Bare IDs only
--in, if given
URLs in one project, plus bare IDs
that project, if --in names it too
URLs in different projects
none
When there is no scope, an agent mention is left as text with the unresolved-mention notice; it is never silent. Tokens the posting loop skips (neither a URL nor a number) are skipped here too. A named --in is resolved only when an agent lookup actually needs the project.
The refusal itself, which was the open design question, no longer exists.
After that change, Codex found a crash when --in is blank, which only happened with the new scope (thread). It is fixed, and its test panicked before the fix.
Still declined, re-checked: schedule updates take the mention scope from the URL's project first. The steelman is that the command's own resolved project should win. But in schedule update, resolvedProjectID is only used for breadcrumbs, while the entry being edited is the one the URL names. That entry's project is the one whose agents can see it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commandsCLI command implementationsskillsAgent skillstestsTests (unit and e2e)
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An
@Name/@First.Lastmention of an agent never reaches it. Fuzzy mentions resolve only against the pingable set (GET /circles/people.json), and an agent can never be pinged by design, so the name always misses. The line posts as plain text with anUnresolved mentions left as textnotice and exit 0.[@Name](person:ID)with an agent's ID goes through the same pingable set and fails the whole command withPerson not found.Tracked on Basecamp: CLI can't @Name-mention an agent.
Fix
@Name: when the pingable set has no exact answer and the command has a project in scope, the agents on that project are candidates too. They come fromGET /projects/:id/people.json, which lists agents and includes theattachable_sgida mention needs. The project comes from, in order: the project the command already resolved (messages create,cards create,schedule create/update,todos sweep,chat postwithout--room), then the item URL's bucket, then--in. The configured default project is not used for a bare ID, because it says nothing about where that item is. Acomments createbatch posts exactly as before, and its agent scope is only a project every target is known to be in: the shared URL project,--infor bare IDs, or both when they agree. When its URLs name different projects it gets no scope. A project that can't be resolved when an agent lookup needs it fails the command and is not treated as an unresolved mention.@Quincyreaches the agent Quincy and not a pingable "Quincy Jones". In every other case people keep precedence, and an agent matches on a partial name only when no person matches at all. Ambiguity is still reported, and resolves to unresolved text as before.personable_type: "Agent"are taken from the project list, so a person who is not pingable still can't be mentioned.[@Name](person:ID): on a pingable miss, oneGET /people/:idcall, accepted only when the person is an agent. It needs no project. A failure of the pingable fetch still fails the command and is not read as a miss.WithDiagnostic, which puts it in the JSONnoticeand on stderr in--agent/--quietmode (TestChatPostAgentModeWarningOnStderr). The notice now also says how an agent can be reached:(agents match by name only among the people on the command's project; name it with --in or a URL if the command has none; [@Name](person:ID) needs no project).basecampskill's mention section now describes agent resolution.messages.gogets the same three-line change as the other call sites (a scope argument, plus the URL's bucket formessages update), and nothing else in that file changes.Test
internal/names/mention_test.goruns a fake server where the agent is in the project's people and not in the pingable set. It checks: resolution by name with a single fetch per list; that a pingable hit makes no project call and never resolves the scope; that a non-pingable person in the project stays unmentionable; that nothing resolves without a scope; exact-agent-over-partial-person; agent ambiguity; that project and pingable API failures stay hard while a failed agent lookup keeps a person match; and agent resolution by person ID.internal/commands/mention_agents_test.gorunscomments createwith a card URL, with--in, and with a bare ID (no project, so the notice with the hint appears), plus[@Quincy](person:7)andchat post --room --in. Each one failed onmain(plain text, orPerson not found: 7).make check: the same results as a cleanorigin/mainrun on this machine, apart from the new tests. The failures on both are TTY/environment tests (wizard first-run, connect doctor, delete-confirmable, bridge token).