[DOCS] Undocumented `ccd_*` MCP servers: session-to-session messaging and `list_sessions` scope

Status Fixed / completed
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 29, 2026 · closed Aug 16, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Documentation Type

Missing documentation (feature not documented)

Documentation Location

https://code.claude.com/docs/en/agent-sdk/sessions.md

Section/Topic

Session management — the ccd_* MCP servers, and session-to-session messaging

Current Documentation

The docs cover several adjacent capabilities, none of which is the one below:

  • Agent teams — a lead session and its spawned teammates share a task list and mailbox. Inter-agent, but within one orchestrated session.
  • Channels — push events into a session from an external system. Opposite direction.
  • Agent SDK session functions — list_sessions(), get_session_info(), rename_session(), tag_session() for reading/modifying local session metadata. No messaging.
  • /teleport and /remote-control — move or drive a single session across surfaces.

None of these describes sending a message from one live session into a different concurrent session's transcript.

What's Wrong or Missing?

A Claude Code desktop session exposes a set of MCP servers prefixed ccd_ that appear in no public documentation I could find:

  • mcp__ccd_session_mgmt__* — list_sessions, send_message, get_session, archive_session, set_session_title, search_session_transcripts, list_events
  • mcp__ccd_session__* — mark_chapter, spawn_task, dismiss_task, read_widget_context
  • mcp__ccd_directory__request_directory

send_message's own tool description reads:

Send a message to another CCD session. The message arrives in the target session as a user turn labelled "From {this session's title}" with a link back here… Unavailable in unattended sessions (scheduled-task runs and remote-dispatched sessions), and cannot deliver to them either.

Using it returned: Message queued for session <id>; it will be processed after the in-flight turn finishes if that session stays healthy.

Three things are undocumented, and the third caused me to give a user an unsupported answer:

  1. What "CCD" stands for. It appears only as a tool-name prefix and inside tool descriptions. "Claude Code Desktop" is a guess.
  2. That session-to-session messaging exists at all, and how it differs from agent teams / channels / teleport.
  3. Which sessions list_sessions enumerates. On a machine with 9 active git worktrees it returned exactly 1 session. That could mean it only registers desktop-app sessions (excluding the VS Code extension and plain claude terminal runs), or it could mean ended sessions drop out, or that the registry is keyed on something narrower than I assumed. I could not distinguish these, and told the user VS Code sessions are unreachable — a claim I could not substantiate.

Practical consequence: an agent asked to hand off context to a peer session cannot tell whether a target is absent because it is unaddressable by design, or because it simply is not listed. The failure is silent — you address the one session you can see, which may be a bystander.

Suggested Improvement

Documentation should state:

  1. Expand "CCD" on first use, and say which surfaces provide these MCP servers (desktop app only? version/flag gated?).
  2. Document send_message semantics, and contrast it with the adjacent features above so it is clear which tool to reach for:
  • delivery is queued, not synchronous — what "if that session stays healthy" means, and whether failure is observable
  • the message lands as a user turn in the target, i.e. it can cause another agent to act
  1. Define list_sessions scope explicitly — which session types register, whether VS Code-extension and terminal sessions are included, and what causes a live session to be absent. A single sentence such as "only sessions started in the desktop app appear; extension and terminal sessions are not addressable" would settle it.
  2. State whether any supported handoff to a VS Code-extension or terminal session exists, even if the answer is "no".

If these servers are intentionally internal and not for general use, saying so is equally useful — a tool that is visible to the model but undocumented invites exactly the kind of confident-but-unfounded explanation I gave.

Impact

Medium - Makes feature difficult to understand

Additional Context

Observed on the Claude Code desktop app, late July 2026, in a repo with multiple concurrent worktrees. I looked for prior issues on ccd_session_mgmt, send_message session, and list_sessions and found none.

Filed after a claude-code-guide lookup confirmed the gap: it located agent teams, channels, and the Agent SDK session functions, and reported that the ccd_* servers and session-to-session messaging are absent from the public docs.

Related docs: Agent SDK sessions · Agent teams · Channels

View original on GitHub ↗

3 Comments

wshallwshall · 2 months ago

Adding a measured follow-up to point 3, since I have now instrumented this across a multi-worktree repo and can answer part of what I could not substantiate when I filed.

There are two session-id namespaces, and nothing published maps between them

  • The ccd_session_mgmt tools (list_sessions, get_session, send_message) use local_<uuid-A>.
  • The on-disk record <config-root>/sessions/<pid>.json uses a bare <uuid-B>. So does the session_id in a hook payload, and the transcript filename <config-root>/projects/<slug>/<uuid-B>.jsonl.

uuid-A is not uuid-B, not a prefix of it, and not derivable from it. Four surfaces agree on one id; the messaging tools use a fifth, unrelated one.

Measured

Four desktop sessions, one repo, each in its own git worktree. Working directories redacted to a placeholder root; ids verbatim.

| working directory | id in the record, hook payload and transcript filename | id in list_sessions |
|---|---|---|
| C:\src\repo\.claude\worktrees\wt-a | 3a6a1257-e9f9-48af-b589-13aed4969ce3 | local_b0cdbba2-e603-4b91-92aa-29359f4b33fb |
| C:\src\repo\.claude\worktrees\wt-b | a26535e7-c2c4-400a-8795-6fd0bdd43023 | local_2a6b9f91-75c7-41bd-8c28-0a47d982e8d9 |
| C:\src\repo\.claude\worktrees\wt-c | 5de3b752-eb76-4353-8bac-b86a96a70822 | local_cb9aab56-d899-4198-85c6-b7703969b0cf |
| C:\src\repo\.claude\worktrees\wt-d | d88d58f9-7bff-440c-a950-4a601457a4d1 | local_598c2775-4524-4623-b3f0-d6161acd132b |

Four things that follow, each checked rather than assumed:

  1. No id relationship. Coordination tooling commonly prints the first 8 characters of the record id (3a6a1257). That prefix appears nowhere in the corresponding messaging id. Stripping local_ does not help: the remainder is a different uuid.
  2. The record id is the one everything else already uses. For wt-c, 5de3b752-... is also the transcript filename, and the same value arrives as session_id in hook payloads. So the messaging namespace is the outlier, not the record.
  3. Branch does not join them. For 2 of the 4 sessions, the branch returned by list_sessions was not the checkout's current branch (git -C <cwd> branch --show-current disagreed) -- the roster appears to carry the branch recorded earlier in the session's life. Branch is unusable as a correlation key, which is easy to discover only after trusting it.
  4. cwd joins them, and only exactly. The cwd in both rosters was byte-identical for every session, so exact case-insensitive equality works. Prefix or substring matching does not: in a worktree layout every worktree cwd is a string extension of the primary checkout's path, so a session sitting in the primary prefix-matches every worktree row and resolves to an arbitrary one.

Actual vs expected

Actual. Holding an id from the record, a hook payload, or a transcript filename and passing it to get_session or send_message returns Session <id> not found. and delivers nothing. Neither roster indicates a second id exists.

Expected. Either the tools accept either id, or each roster carries the other's id so the two can be joined by something more robust than a filesystem path.

Worth stating plainly, because it is the good news: a wrong id fails loudly. The dangerous failure is the workaround, not the error. Since cwd is the only join key, a fallback to prefix matching resolves the wrong session, and send_message then delivers a user turn into an uninvolved session's transcript with no error at all. The one available join key is also the one whose misuse is silent.

Minimal repro

  1. Open two or more desktop sessions on one repo, each in its own git worktree.
  2. From one, call list_sessions. Note a peer's sessionId and cwd.
  3. Read <config-root>/sessions/*.json, find the record whose cwd equals that cwd, note its bare-uuid sessionId, and note the same uuid naming that session's transcript under <config-root>/projects/<slug>/.
  4. Call get_session or send_message with the bare uuid. Result: Session <id> not found.
  5. Look for any field common to the two rows other than cwd. branch is present in both and can disagree; nothing else correlates.

One correction to my own earlier writeup

An internal note of mine asserted this failure was silent. That was an inference stated as a measurement. It is not silent, and I have corrected it.

For the docs ask above: a sentence stating that the MCP session id is distinct from the id every other surface uses, and that cwd is the only reliable join key and must be matched exactly, would close point 3. Happy to split this into its own issue if you would rather track the id relationship as a behaviour question than a docs one.

bcherny collaborator · 1 month ago

Thanks for the detailed report — this is now documented.

The desktop session-messaging surface is described at https://code.claude.com/docs/en/desktop#work-across-sessions. It covers the scope of what Claude sees (only sessions the desktop app itself runs — local, SSH, and WSL Code-tab sessions — not terminal CLI, VS Code, or cloud sessions, which is why one desktop session next to nine terminal worktrees lists exactly one), the 20-most-recent default, how a message shows up in the target session (a card labeled with the sending session's title, held until the current turn finishes), and the unattended-session restriction.

Messaging between independent Claude Code sessions, including terminal ones, is a separate feature (v2.1.224+): https://code.claude.com/docs/en/cross-session-messaging

Closing as answered — reopen if something is still missing.

🤖 Generated with Claude Code

github-actions[bot] · 10 days ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.