[BUG] Claude Desktop (Code tab) auto-denies CLI-native SendMessage, breaking subagent resumption; the desktop replacement only covers session-to-session

Status Open
Reported on v2.1.260
Maintainer reply None cached
Activity 8 comments · opened Sep 4, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Desktop for macOS 1.46388.1 auto-denies the CLI-native SendMessage tool in every Code-tab session. Because that one tool serves both peer-session messaging and subagent resumption, the block removes subagent resumption entirely, and the desktop-provided replacement only covers the first of the two.

The desktop app does two things at session start:

  1. It launches its bundled CLI with --disallowedTools SendMessage.
  2. It installs a PreToolUse hook that hard-denies the tool with reason cli_native_send_message, emitting telemetry desktop_ccd_permission_auto_denied.

The deny reason (verbatim from Claude.app/Contents/Resources/app.asar) reads:

Use mcp__ccd_session_mgmt__send_message to message another session from the desktop app; the CLI-native SendMessage tool is unavailable here (terminal sessions outside Claude Desktop are not reachable from this session).

The stated rationale is entirely about peer sessions, and the named replacement mcp__ccd_session_mgmt__send_message targets sessions by session_id only. It cannot address a subagent. So the intentional part of this change (peer messaging) has a replacement; the unintentional part (subagent resumption) has none.

This is precisely the coupling the documentation warns about: the cross-session-messaging page notes that denying SendMessage also removes messaging to subagents and agent-team teammates, since "the same tool serves both". That page documents no desktop-specific limitation, and the sub-agents page continues to present SendMessage as the way to resume a subagent.

The Agent tool's own result text still instructs the model to use it:

agentId: a325fd90b53ee7a56 (internal ID - do not mention to user.
Use SendMessage with to: 'a325fd90b53ee7a56', summary: '<5-10 word recap>' to continue this agent.)

ListAgents is still available and still lists subagents as addressable. Only the tool that would act on that listing is gone.

What Should Happen?

The desktop app should deny SendMessage only for peer-session targets, and keep it available for subagents and agent-team teammates within the session. Those targets are in-process and unaffected by the "terminal sessions are not reachable" rationale.

If a blanket deny is intended, then the Agent tool result text, ListAgents, and the docs should stop pointing at a tool that cannot be called, and a desktop-side path to resume a subagent should exist.

Error Messages/Logs

Every call, including from subagents:

Error: No such tool available: SendMessage.
SendMessage is disabled for this session, in subagents as well as here.

Launch command line of the app-spawned CLI process (abridged):

~/Library/Application Support/Claude/claude-code/2.1.260/claude.app/Contents/MacOS/claude \
  --output-format stream-json --input-format stream-json \
  --resume=<session-uuid> \
  --allowedTools mcp__computer-use,mcp__ccd_session__spawn_task,...,mcp__ccd_session_mgmt__send_message,... \
  --disallowedTools SendMessage \
  --permission-mode bypassPermissions

Deny hook in Claude.app/Contents/Resources/app.asar:

if (!Ou.includes(e.tool_name)) {
  if (e.tool_name === "SendMessage")
    return t.jb("desktop_ccd_permission_auto_denied", {
      session_id: r, tool_name: e.tool_name, reason: "cli_native_send_message"
    }), { hookSpecificOutput: { hookEventName: "PreToolUse",
           permissionDecision: "deny", permissionDecisionReason: ... } };

Steps to Reproduce

  1. Open Claude Desktop 1.46388.1 on macOS, Code tab, any project.
  2. Spawn a subagent with the Agent tool. The result text hands back an agentId and instructs you to continue it with SendMessage.
  3. Call SendMessage with that agentId.
  4. Every call is refused with the message above, in the main session and inside subagents alike.

Control test, the same call from a plain Terminal CLI session on the same machine:

$ claude -p 'call SendMessage with to="a0000000000000000", summary="test", message="test"'
Agent "a0000000000000000" could not be resumed: No transcript found for agent ID: a0000000000000000.

That is the response of a tool that exists and cannot find its target, so the CLI is fine and the block is entirely app-side.

Is this a regression?

Yes.

Last Working Version

Claude Desktop 1.46388.0 (the version before this update), with bundled Claude Code 2.1.258.

Timeline on this machine, all local time, 2026-09-04:

| Time | Event |
|---|---|
| 00:18:24 | last successful SendMessage call |
| 00:24:26 | Claude.app bundle rewritten (update to 1.46388.1 staged) |
| 06:56:56 | first launch of 1.46388.1; CLI 2.1.260 unpacked |
| 08:42 | first refusal |

289 successful SendMessage calls across this machine's transcripts, every one of them before 06:56:56, none after.

Version

Claude Desktop 1.46388.1 (macOS), bundled Claude Code 2.1.260. Also reproduced against the standalone CLI 2.1.259 when launched by the app. The CLI version is not the variable, the app is.

Impact

This breaks any multi-agent workflow in the desktop app that iterates with a subagent rather than firing one off: review and repair loops, a writer and a checker exchanging findings, any long-running agent asked a follow-up question. Each round now costs a fresh full-context dispatch, and the agent's accumulated context is lost every time.

View original on GitHub ↗

8 Comments

DennisPh1977 · 21 hours ago

Follow-up with measurements, because the stated rationale does not survive them.

The deny reason justifies removing SendMessage with a parenthetical about reachability:

(terminal sessions outside Claude Desktop are not reachable from this session)

Three checks on this machine, all against real data rather than assumption.

1. Real-world usage of the removed tool was not cross-session at all

Every SendMessage call across all 485 transcripts on this machine, classified by target:

| Target type | Calls |
|---|---|
| Subagent by agentId | 204 |
| Subagent by the name given at spawn time | 192 |
| Any other session | 0 |

396 calls, every one of them to a subagent inside the same session. Not a single message ever left a session. The reachability argument does not describe this workload at all.

2. Subagent messaging never crosses a process boundary

A subagent runs in the same process as the session that spawned it. Claude Code's own internals call it exactly that:

[SendMessageTool] In-process teammate ... approving shutdown - signaling abort

There is no socket, no second session and no machine boundary involved, so there is nothing that could be unreachable. This is the part of SendMessage that the desktop app removed, and it is the part the rationale does not touch.

3. Even the peer case is reachable

Both a desktop session and a Terminal CLI session on this machine bind their inbox sockets side by side, same directory, same owner, same permissions:

$ ls -la /tmp/cc-socks/
srw-------  1 <user>  wheel  0  Sep  4 09:00  <pid-a>.sock   <- Terminal CLI session
srw-------  1 <user>  wheel  0  Sep  4 09:37  <pid-b>.sock   <- Claude Desktop Code-tab session

The desktop session knows its own socket (CLAUDE_CODE_MESSAGING_SOCKET=/tmp/cc-socks/<pid-b>.sock), and per the cross-session-messaging docs that socket is exactly how same-machine delivery works. ListAgents, which the app still permits, also lists local interactive peer sessions from inside a desktop session.

What this adds up to

The parenthetical describes a limitation of the replacement, not of the tool that was removed. mcp__ccd_session_mgmt__send_message addresses sessions by session_id through the app's own session registry, so it is the tool that cannot see a terminal session. That limitation was then written into the justification for removing a tool which (a) in practice was used almost entirely for in-process subagent messaging and (b) handled the named cross-session case over a socket that demonstrably exists.

The replacement also cannot address a subagent at all: subagents have an agentId, not a session_id. And mcp__ccd_session__spawn_task only surfaces a suggestion chip for a human to click, so a desktop session cannot even create the worker sessions that the replacement could address.

Net effect for desktop users: an in-process capability was removed on the basis of a cross-process argument that does not apply to it, with no replacement for the capability actually lost.

neneonline · 20 hours ago

Independent confirmation from a second machine and account, with usage data that runs opposite to yours — which I think strengthens the case rather than complicating it.

Same regression, same window

macOS. Claude Desktop 1.46388.1 (sentry-release=Claude@1.46388.1), Code tab, CLAUDE_CODE_ENTRYPOINT=claude-desktop.

| | Bundled CLI | SendMessage |
|---|---|---|
| Session at 10:12 local | 2.1.258 | 11 successful calls |
| Session at 10:41 local | 2.1.260 | No such tool available |

Same app, same account, same project, 29 minutes apart. Both sessions entrypoint: claude-desktop, userType: external. The only variable is the app update.

Error text matches yours verbatim, including the subagent clause:

No such tool available: SendMessage.
SendMessage is disabled for this session, in subagents as well as here.

app.asar on this machine contains the hook you extracted (reason: "cli_native_send_message", permissionDecision: "deny"), and nearby var xu="SendMessage", Su=["ListAgents","ListPeers"] — the separate lists explain why ListAgents survives and still advertises targets that can no longer be addressed.

Where my data differs from yours

You measured 0 cross-session calls in 396. On this machine, across 492 SendMessage calls in all transcripts:

| Target | Calls |
|---|---|
| Subagent (agentId or builtin type) | 333 |
| Peer by session name | 131 |
| Peer via bridge: address | 19 |
| Peer via local socket (uds:) | 9 |

159 of 492 (32%) were peer sends. Of those, 115 crossed a machine boundary — 96 addressed by name to Remote Control sessions on a Windows VM (Windows rewrite 2 ×38, Windows rewrite 7 ×13, and so on), plus 19 via bridge: addresses.

So the cross-machine case the deny reason calls unreachable was not hypothetical here. It was a third of the workload, it worked, and it stopped working at the update. Your machine shows the in-process subagent case being destroyed; this one shows the cross-machine peer case being destroyed. Both are removed by the same hook.

The replacement does not cover it

mcp__ccd_session_mgmt__send_message addresses by session_id drawn from mcp__ccd_session_mgmt__list_sessions. On this machine that listing returns local sessions only — no Remote Control peers appear in it, so the Windows sessions cannot be named, let alone addressed. ListAgents lists them; the replacement cannot see them.

Inbound is unaffected, which makes the break one-directional

The Windows session sent a message to this desktop session after the update and it was delivered normally, arriving over the session's bound inbox socket. Receiving works; sending does not. Per the docs those are separate controls, so this is expected once the tool is denied — but the practical result is a session that can be talked to and cannot answer, while still listing everyone it could theoretically reach.

Net

Two machines, two different usage profiles, same hook, and in both cases the capability actually lost is one the stated rationale does not describe.

aunitt · 18 hours ago

<!-- cspell:words asar mgmt behaviour -->
Confirming this on a second macOS machine, with one addition to the version
range and a third loss profile. Here the capability destroyed is messaging
between desktop Code-tab sessions and terminal CLI sessions
, and the named
replacement cannot address that direction at all.

Still present in 1.46388.2

The body and the existing comment both cite 1.46388.1. This machine is on
1.46388.2, so the change has survived a further app update. The launch flag
is verifiable directly from the process table while the app is running:

$ ps -Ao command= | grep -c "disallowedTools SendMessage"
6

Six app-spawned CLI processes, each carrying --disallowedTools SendMessage, on
1.46388.2. The app.asar hook and the Su=["ListAgents","ListPeers"] keep-list
are both present as quoted.

The lost capability here is desktop-to-CLI messaging

One desktop Code-tab session on this machine, running 2026-09-01 to 2026-09-03:

| Measure | Count |
| --- | --- |
| SendMessage calls | 124 |
| succeeded | 119 |
| distinct peer targets | 17 |

None of those were subagent resumptions. Every target was a peer session, and
all but one were started outside the desktop app: background sessions
launched from a terminal, Remote Control sessions, and bridge sessions.

The measurement that decides whether the replacement covers this is how many of
those 17 targets mcp__ccd_session_mgmt__send_message can actually address. It
takes a session_id drawn from mcp__ccd_session_mgmt__list_sessions, so I
compared the two sets:

| Target set | Count |
| --- | --- |
| distinct targets that worked before the update | 17 |
| addressable via the replacement | 1 |
| not addressable | 16 |

The single addressable one is itself a desktop Code-tab session. Every
terminal-side target is absent from list_sessions, which returns only
local_-prefixed desktop sessions.

So the desktop-to-CLI direction has no replacement path. That direction is the
useful one, and it is much of the reason to have the tool: a Code-tab session
coordinating work that is running in terminals, where the long jobs live.
ListAgents still lists all of those sessions as addressable targets, which is
how the loss presents itself, as a listing full of peers with no way to reach
any of them.

Worth noting against the deny reason's own wording, which says terminal sessions
outside Claude Desktop are not reachable from this session: on this machine they
demonstrably were, the previous evening, 119 times.

Nothing on the CLI side gates this

All three CLI builds present on this machine define the tool with no
isEnabled gate at all
. Extracting the tool object from each binary and
testing for the property:

| CLI build | isEnabled present |
| --- | --- |
| 2.1.258 | no |
| 2.1.259 | no |
| 2.1.260 | no |

The tool is unconditionally defined and marked shouldDefer: true in each. That
is consistent with the terminal control test in the body, and it means the CLI
never opted into this behaviour in any of the three builds the app has shipped
with.

The hook's explanation never reaches anyone

Because --disallowedTools removes the tool before any hook runs, the
permissionDecisionReason quoted in the body is unreachable. What is actually
surfaced is the No such tool available text, which names no replacement.

That matters on its own, separately from the scoping decision, because the deny
reason is the only place the app tells the user or the model that
mcp__ccd_session_mgmt__send_message exists. As shipped, the guidance written
for exactly this moment cannot be displayed.

Timeline on this machine

All local time.

| Time | Event |
| --- | --- |
| 09-03 19:25 | last successful send from the desktop session |
| 09-04 03:56 | app update installed |
| 09-04 08:49 | same session resumed, tool absent, no attempt possible |
| 09-04 09:49 | further update to 1.46388.2, still absent |

Agreeing with the requested outcome, with one addition. Scoping the deny to peer
targets would still remove the case above, since these are peer targets. What
is needed is for the deny to be scoped to whatever the app genuinely cannot
route, and for the replacement to be able to see terminal sessions before the
tool that can reach them is taken away.

aunitt · 18 hours ago

It's worth me adding that this has broken a particular workflow for me that had been keeping me on Claude Code rather than moving to a competitor. I have been running with a single coordinator session running in the desktop app that has been directing and coordinating CLI based sessions. Now I can no longer do this, I have less need to keep me in the Claude system. If I can't communicate between sessions, then why stay on Claude and not switch?

DanielIshi · 11 hours ago

Confirming on Windows, plus a same-machine A/B

Reproduced on Windows 10 Pro 19045, Claude Code v2.1.258, Opus 5, Claude Pro — so this is not macOS-only.

Desktop app, Code tab:

  • env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1" in ~/.claude/settings.json
  • permissions.deny empty in both settings.json and settings.local.json, no disabledTools entry
  • ListAgents works and returned 17 peer sessions plus the session's own subagents, and its description names SendMessage as the way to address them
  • calling it: Error: No such tool available: SendMessage. SendMessage is disabled for this session, in subagents as well as here.

The A/B that makes the surface boundary explicit: on the same machine, at the same time, a claude CLI session started from Windows Terminal in the same user account can use SendMessage. It listed the same peers and delivered a message to another session — including to a Remote Control session on a different machine, with a returned msg_id, and the receiving session processed it and answered. So the capability is present in the account and the peer network; only the Desktop Code tab denies it. That matches the reported --disallowedTools SendMessage plus the cli_native_send_message PreToolUse hook.

What the missing back-channel costs, from one working session today:

  • A build subagent spent several minutes diagnosing "something external is power-cycling this machine" — it was the lead agent and the user performing a planned RAM upgrade. There was no way to tell it.
  • The same subagent later described the lead's own process as "an independent second agent" and cited it as independent confirmation of its own build.
  • Two subagents collided on TCP port 11596/11597 because the lead could not tell the second one the port was taken.

None of these are edge cases; they are the normal failure mode of fan-out without a return path. mcp__ccd_session_mgmt__send_message does not cover them: it addresses sessions by session_id only, cannot address a subagent, and an idle target receives the message but does not act on it until a human opens that session.

Two further Windows data points:

  • CLI sessions started from cmd or wt do not appear in ccd_session_mgmt.list_sessions, not even with CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1 (which does remove the "Transcript saving is off" warning). They do appear in ListAgents. This may be the same gap as #89658.
  • The short hex ids ListAgents prints (e.g. dfd18d) are not accepted by ccd_session_mgmt.send_message, which answers "Session not found" — including for the full local_<uuid> form of a CLI session.

Lineage: this looks like the same class as #35240, which was closed as completed on 2026-07-01 with #28048 (VS Code), #56449 (web), #48160 and #42737 folded in as duplicates. That fix evidently does not cover the Desktop Code tab. Recording the link since #92016 does not currently reference it.

Digital-Leprechaun · 10 hours ago

Same regression on Windows, and it also breaks messaging to Remote Control peers, not only subagents.

Environment: Claude desktop app 1.46388.2 (MSIX), bundled CLI 2.1.260, Windows 11 Pro 26200.

Symptoms in a Code-tab session:

  • SendMessage is absent: not in the tool list, not in the deferred list, ToolSearch finds nothing.
  • ListAgents works and lists Remote Control peers on another machine (a Linux CLI session started with claude --remote-control --name ...), including their idle/offline state.
  • Inbound works: cross-session messages from that Linux peer arrive in the desktop session as <cross-session-message from="bridge:session_...">.
  • Outbound is impossible: the substitute mcp__ccd_session_mgmt__send_message only accepts local desktop-app session ids; the Remote Control peer is not in list_sessions, so there is no address to send to.
  • Not caused by settings: no deny rules for SendMessage/ListAgents in user, project or local settings; no managed settings; none of DISABLE_TELEMETRY / DO_NOT_TRACK / CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC / DISABLE_GROWTHBOOK set.

Control: a terminal session on the same 2.1.260 binary (%APPDATA%\Claude\claude-code\2.1.260\claude.exe --remote-control --name ...) has SendMessage and messages the Linux peer fine. Sessions from the previous app build (CLI 2.1.258) also had it. So the loss is specific to how the desktop app launches the CLI, consistent with the --disallowedTools SendMessage finding above.

Impact: a two-machine workflow (Windows desktop app + Linux --remote-control CLI) that was working yesterday is now one-directional. Resuming or reconnecting Remote Control on the desktop session restores the peer list but not the tool.

Suggested fix: either stop disallowing SendMessage in desktop sessions, or extend mcp__ccd_session_mgmt__send_message to accept Remote Control peer addresses (the bridge:session_... / ListAgents names) and subagent ids.

tirregbo · 6 hours ago

Confirming this from a Windows desktop against a Linux host, and adding a data point that I think matters for triage: the named replacement does not cover the peer-session case either. The message is never handed to the receiving model at all — it exists only in the UI.

The issue body notes that the intentional part of this change (peer messaging) has a replacement in mcp__ccd_session_mgmt__send_message. In my setup that replacement accepts the message, reports success, renders it in the target session's UI — and the model in that session never receives it, then or later.

Setup

  • Claude for Windows 1.46388.3 (same desktop build as #92183, which reported it on macOS), driving a headless Debian 13 host over remote; bundled CLI 2.1.260
  • Two long-lived interactive Code-tab sessions on that host, different working directories, used as a two-agent pair: one does the work and files a handover note, the other ingests it and pushes back on unsupported claims
  • Both launched with --permission-mode bypassPermissions

Same command line as reported here, verified in /proc/<pid>/cmdline of both sessions:

--allowedTools mcp__computer-use,...,mcp__ccd_session_mgmt__send_message,...
--disallowedTools SendMessage
--setting-sources=user,project,local

No deny rule in user, project or local settings (grep -r disallow over settings.json / settings.local.json: no match), and ToolSearch select:SendMessage returns nothing — the tool is genuinely absent, not denied at call time.

The replacement does not deliver

Six messages were sent between the two sessions with mcp__ccd_session_mgmt__send_message, addressed by the session_id from list_sessions. The target was verified independently (/proc/<pid>/cwd), and ListAgents lists the peer correctly, so discovery and addressing are not the problem.

  • Every call returned success, most with "sent … but it has not acknowledged it yet".
  • None of them triggered a turn in the receiving session.
  • The human operator saw all of them rendered in that session's UI.
  • Prompted by hand afterwards and asked explicitly whether it could see the message, the receiving model could not.

The receiving session's transcript on disk shows why, and this is the part I'd point triage at. In that JSONL, every prompt the human typed appears as a queue-operation / enqueue entry with its verbatim content, e.g.:

{"type":"queue-operation","operation":"enqueue","timestamp":"...","content":"was liegt bei Dir?"}

Not one of the six delivered messages appears anywhere in that file — no user turn, no enqueue, no entry of any kind. (Their text does occur inside tool_result blocks, but only because that session later read the sender's transcript while investigating; that is its own tool output, not a delivery.)

So the message is not merely failing to wake the session, and it is not sitting in a queue waiting to be picked up on the next turn. It never enters the receiving session at all. The sender is told it was sent, the human sees it on screen, and the model it was addressed to has no path to it.

This is the same UI/context divergence as #91671, which observed it cross-machine over Remote Control. Here it happens between two desktop sessions on one machine, which is precisely the case the replacement is documented to handle.

When it changed

Narrowed from the session transcripts on the host, matching real SendMessage tool calls against each session's "version" field (times CEST):

| Window | CLI version | SendMessage |
|---|---|---|
| 2026-09-02 13:28 – 2026-09-04 19:59 | 2.1.255 / 2.1.258 / 2.1.259 | used in 9 sessions, last call 2026-09-04 19:59:16 |
| from 2026-09-04 20:51 | 2.1.260 | zero calls; tool absent from the tool list |

The desktop app updated to 1.46388.3 at 16:20 that day and the 2.1.260 binary landed at 13:15; sessions started from ~20:51 on show the new behaviour. Cross-session messaging had worked reliably for days before that, on the same machine, between the same two sessions.

What I'd ask for

Any one of these restores the workflow; the current state is the worst of the three:

  1. the replacement actually delivers into the receiving session's context and wakes it, or
  2. it reports honestly when it has not, instead of returning success, or
  3. SendMessage stays available for same-host peer sessions.

As it stands, a human has to sit between the two sessions and repeat by hand what each produced for the other — for every single handover.

Related: #92183 (same flag, same desktop build, macOS), #91671 (delivered-but-not-in-context, cross-machine).

andy-wassyng · 3 hours ago

Confirming on macOS 1.46388.2 (bundled CLI 2.1.260), with one fact not yet in the thread: the next desktop update, 1.46388.3, is already staged on this machine and still carries the same denial.

  • ~/Library/Caches/com.anthropic.claudefordesktop.ShipIt/update.*/Claude.app/Contents/Resources/app.asar contains cli_native_send_message and the same xu="SendMessage",Su=["ListAgents","ListPeers"] block as the installed 1.46388.2. So the coming relaunch will not restore the tool.
  • Timeline on this machine matches the thread: 112 successful SendMessage calls to subagents in one long-running session, the last at 2026-09-04 03:45 UTC; ShipIt installed 1.46388.2 at 16:09:58 UTC, relaunched the app, and the session's claude process came back five seconds later with --disallowedTools SendMessage.
  • One more surface pointing at the missing tool: ToolSearch("select:SendMessage") answers "No matching deferred tools found" rather than saying the host disabled it, so a model spends turns trying to load a tool that cannot exist in the session.

Impact here is the orchestration case others describe: a coordinator session running multi-hour build subagents can no longer relay a mid-flight product decision to one; the choice is to let it finish on a superseded brief or kill it and lose its context. Workaround in use: dispatch retaskable work as separate desktop sessions via mcp__ccd_session__spawn_task and message those with mcp__ccd_session_mgmt__send_message.