Fix: First message to a peer fails, because the docs say to use a bare name - #619
Conversation
The messaging guidance shipped in #605 told readers to use a peer's bare name when only one row answers to it, and to reach for the `[ref]` only to break a tie between same-named rows. That fails on first contact. Measured against Claude Code 2.1.227/2.1.228 on two independent peers, each with a name unique in the listing: a bare-name send is refused with `'<name>' is not an agent in this conversation. Re-send with the ref to confirm you mean: <name> [<ref>]`, and nothing is delivered. Uniqueness does not help — the refusal names a single candidate and still fires. After a message to that peer has gone through, its bare name resolves. So a reader following the shipped text loses a round trip on every first contact, having been told the bare name would work. The guidance is now written operationally — what to do and what you will see — rather than asserting a mechanism inside Claude Code that this repo cannot maintain: address a peer you have not messaged before as `name [ref]`, expect a refusal that names the ref you need, and know the bare name works thereafter. The ref remains the picker when rows share a name, which TBD makes ordinary since every session in a worktree takes that worktree's display name. The skill-content test now pins the addressing form and the refusal together; text describing the ref as only-for-collisions satisfies neither.
|
✅ Looks good Both review lenses came back clean on this PR. Correctness — checked internal consistency of the rewritten addressing guidance across the three surfaces it touches ( Conventions — this is a documentation/string-constant-only change, so the default-off-flag, TUI-scraping, migration-triple-update, and theory-placement rules don't apply, and confirmed as much. Checked all changed lines for private/org-specific content (the No findings were filtered out during merge (both specialists reported empty findings arrays). Finding dispositionsNo specialist findings were reported by either lens — nothing to dispose of. Review diagnosticsNo tool calls failed or were denied for either specialist or the orchestrator. Both findings files were written successfully and parsed as valid JSON on the first attempt. Posted by the claude-review check — the review of this PR's diff at patch-id |
What's broken
The cross-session messaging guidance shipped in #605 tells a reader to address a peer by its bare name when only one row answers to it, and to reach for the
[ref]only when several rows share a name.Follow that and your first message to any peer fails. The send is refused, nothing is delivered, and you have burned a round trip being told the opposite of what happens.
It is wrong in both places #605 put it: the user-facing page, and the skill text that loads into spawned sessions — so it misleads humans and agents alike.
Why it happens
The
[ref]is not a tiebreak for ambiguous names. It is how you confirm you mean a session outside your own conversation.Measured against Claude Code 2.1.227 / 2.1.228, on two independent peers, each with a name unique in the listing:
Uniqueness does not help — the refusal names a single candidate and fires anyway. After a message to that peer has gone through, its bare name resolves.
The wrong text came from reading the tool's description rather than exercising it.
When it broke
#605, which introduced the guidance. It has never been correct.
What this PR does
Rewrites the addressing guidance on all three surfaces to describe what a reader should do and what they will see:
docs/cross-session-messaging.md— section retitled from "Addressing a peer when names collide" to "Addressing a peer", since collisions are only half of it. Read the listing, address an unmessaged peer asname [ref], expect the refusal (quoted verbatim), know the bare name works afterwards. The collision case is kept, with the ref as the picker. The fresh-listing caution is now motivated: refs belong to live sessions.Sources/TBDShared/TBDSkillContent.swift— same rule, compressed, since this loads into every spawned session. Net +3 lines.docs/specs/2026-08-09-cross-session-messaging-design.md— one paragraph of "The name is a label; the ref is the address" corrected. The section's core claim was already right; only the sentence scoping the ref to multi-row cases was wrong.Deliberately operational, not mechanistic. The wording says what to do and what you will see; it does not assert that Claude Code keeps a per-peer handshake. An earlier draft of this fix claimed exactly that, on a single before/after observation — which could not distinguish a handshake from the name merely having been ambiguous at first. A peer session challenged the inference, the falsification test above settled it, and the docs now state only what a reader can act on and this repo can maintain.
Assumptions
name [ref]still holds, which is why the guidance leads with the action rather than the error string.Evidence & verification
name [ref],a bare name may be refused, andhave not messaged beforetogether. Mutation-checked — restoring feat: name TBD sessions and unify the Claude peer registry across profiles #605's wording turns all three red, one issue per assertion; collision-only text satisfies none of them. The previous single#expect(body.contains("[ref]"))passed against the wrong text, which is why it did not catch this.name [ref]; one confirmed to accept its bare name on a later send. A refused send delivers nothing, so establishing this cost no peer any context.scripts/test.sh→ 5591 tests in 590 suites passed (1 known issue: theFlakyQuarantineSelfTestsfixture that fails-then-passes by design).scripts/swift-safe buildclean;swiftlint --strict→ 0 violations in 695 files.Two unrelated live-subprocess flakes appeared in earlier full runs, a different one each time (
CodexUsageFetcherLifecycleTests.virtualTimeoutReturnsTimedOutAndKillsChild,ChildReaperTestsbackground reap), and the final run was clean. Neither is plausibly reachable from a string-only change; both belong to the load-sensitive cluster being addressed separately.Note for future edits here
The skill-content assertions pin phrases that must stay contiguous in the Swift multi-line string literal. A cosmetic rewrap of that paragraph broke
worktree display nameandhave not messaged beforeacross line breaks and reddened the suite legitimately. The final wrapping keeps every pinned phrase on one line at ≤80 columns.💬 Cross-Session Messaging