Skip to content

Fix: First message to a peer fails, because the docs say to use a bare name - #619

Merged
cheapsteak merged 1 commit into
mainfrom
fix/cross-session-ref-addressing
Aug 12, 2026
Merged

Fix: First message to a peer fails, because the docs say to use a bare name#619
cheapsteak merged 1 commit into
mainfrom
fix/cross-session-ref-addressing

Conversation

@cheapsteak

Copy link
Copy Markdown
Owner

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:

'acme-worker' is not an agent in this conversation.
Re-send with the ref to confirm you mean:
  acme-worker [<ref>] — Claude session, on this machine, active 11h ago

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 as name [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

  • The refusal text is not a contract. It was measured, not documented. If Claude Code changes the message, our quoted example goes stale — but the instruction to address first contact as name [ref] still holds, which is why the guidance leads with the action rather than the error string.

Evidence & verification

  • Discriminating test: the skill-content assertions now pin name [ref], a bare name may be refused, and have not messaged before together. 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.
  • Field evidence: two independent peers, both refused on a unique bare name, both reachable with 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: the FlakyQuarantineSelfTests fixture that fails-then-passes by design).
  • scripts/swift-safe build clean; swiftlint --strict → 0 violations in 695 files.

Two unrelated live-subprocess flakes appeared in earlier full runs, a different one each time (CodexUsageFetcherLifecycleTests.virtualTimeoutReturnsTimedOutAndKillsChild, ChildReaperTests background 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 name and have not messaged before across line breaks and reddened the suite legitimately. The final wrapping keeps every pinned phrase on one line at ≤80 columns.

💬 Cross-Session Messaging

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.
@tbd-claude-reviewer

Copy link
Copy Markdown

✅ 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 (Sources/TBDShared/TBDSkillContent.swift, docs/cross-session-messaging.md, docs/specs/2026-08-09-cross-session-messaging-design.md), verified the new pinned test assertions in Tests/TBDSharedTests/TBDSkillContentTests.swift genuinely match the new text (and genuinely fail against the old #605 wording, as the PR claims), checked for stale references to the old "bare name is fine when unique" rule elsewhere in the tree (none found), and confirmed the restructured markdown in docs/cross-session-messaging.md is well-formed. No application/daemon logic changes were in scope for concurrency or platform-correctness review.

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 acme-worker / <ref> example follows the sanctioned placeholder convention). Judged the change as a genuine bug fix — correcting docs to match already-existing, unchanged tool behavior — rather than a theory revision, so no new spec doc is required; the existing spec's one wrong sentence is corrected in place. No revision-history language leaked into the shipped doc bodies.

No findings were filtered out during merge (both specialists reported empty findings arrays).

Finding dispositions

No specialist findings were reported by either lens — nothing to dispose of.

Review diagnostics

No 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. .git/shallow exists and lists only the pinned merge-base commit itself, which did not constrain any conclusion either specialist drew (no git log/git blame history walk was needed for this docs-only diff).

Posted by the claude-review check — the review of this PR's diff at patch-id c60341c5a5c5347df638f9a409374c71d75972ac. A newer review comment supersedes this one.

@cheapsteak
cheapsteak merged commit 8884cc0 into main Aug 12, 2026
5 checks passed
@cheapsteak
cheapsteak deleted the fix/cross-session-ref-addressing branch August 12, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant