[BUG] Claude Desktop (Code tab) auto-denies CLI-native SendMessage, breaking subagent resumption; the desktop replacement only covers session-to-session
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:
- It launches its bundled CLI with
--disallowedTools SendMessage. - It installs a
PreToolUsehook that hard-denies the tool with reasoncli_native_send_message, emitting telemetrydesktop_ccd_permission_auto_denied.
The deny reason (verbatim from Claude.app/Contents/Resources/app.asar) reads:
Usemcp__ccd_session_mgmt__send_messageto message another session from the desktop app; the CLI-nativeSendMessagetool 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
- Open Claude Desktop 1.46388.1 on macOS, Code tab, any project.
- Spawn a subagent with the
Agenttool. The result text hands back anagentIdand instructs you to continue it withSendMessage. - Call
SendMessagewith thatagentId. - 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.
8 Comments
Follow-up with measurements, because the stated rationale does not survive them.
The deny reason justifies removing
SendMessagewith a parenthetical about reachability: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
SendMessagecall 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:
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
SendMessagethat 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:
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_messageaddresses sessions bysession_idthrough 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 asession_id. Andmcp__ccd_session__spawn_taskonly 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.
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:
app.asaron this machine contains the hook you extracted (reason: "cli_native_send_message",permissionDecision: "deny"), and nearbyvar xu="SendMessage", Su=["ListAgents","ListPeers"]— the separate lists explain whyListAgentssurvives 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
SendMessagecalls in all transcripts:| Target | Calls |
|---|---|
| Subagent (
agentIdor 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 viabridge: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_messageaddresses bysession_iddrawn frommcp__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.ListAgentslists 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.
<!-- 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:
Six app-spawned CLI processes, each carrying
--disallowedTools SendMessage, on1.46388.2. The
app.asarhook and theSu=["ListAgents","ListPeers"]keep-listare 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 |
| --- | --- |
|
SendMessagecalls | 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_messagecan actually address. Ittakes a
session_iddrawn frommcp__ccd_session_mgmt__list_sessions, so Icompared 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 onlylocal_-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.
ListAgentsstill lists all of those sessions as addressable targets, which ishow 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
isEnabledgate at all. Extracting the tool object from each binary andtesting for the property:
| CLI build |
isEnabledpresent || --- | --- |
| 2.1.258 | no |
| 2.1.259 | no |
| 2.1.260 | no |
The tool is unconditionally defined and marked
shouldDefer: truein each. Thatis 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
--disallowedToolsremoves the tool before any hook runs, thepermissionDecisionReasonquoted in the body is unreachable. What is actuallysurfaced is the
No such tool availabletext, 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_messageexists. As shipped, the guidance writtenfor 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.
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?
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.jsonpermissions.denyempty in both settings.json and settings.local.json, nodisabledToolsentryListAgentsworks and returned 17 peer sessions plus the session's own subagents, and its description namesSendMessageas the way to address themError: 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
claudeCLI 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 returnedmsg_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 SendMessageplus thecli_native_send_messagePreToolUse hook.What the missing back-channel costs, from one working session today:
None of these are edge cases; they are the normal failure mode of fan-out without a return path.
mcp__ccd_session_mgmt__send_messagedoes not cover them: it addresses sessions bysession_idonly, 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:
cmdorwtdo not appear inccd_session_mgmt.list_sessions, not even withCLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1(which does remove the "Transcript saving is off" warning). They do appear inListAgents. This may be the same gap as #89658.ListAgentsprints (e.g.dfd18d) are not accepted byccd_session_mgmt.send_message, which answers "Session not found" — including for the fulllocal_<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.
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:
SendMessageis absent: not in the tool list, not in the deferred list,ToolSearchfinds nothing.ListAgentsworks and lists Remote Control peers on another machine (a Linux CLI session started withclaude --remote-control --name ...), including their idle/offline state.<cross-session-message from="bridge:session_...">.mcp__ccd_session_mgmt__send_messageonly accepts local desktop-app session ids; the Remote Control peer is not inlist_sessions, so there is no address to send to.denyrules for SendMessage/ListAgents in user, project or local settings; no managed settings; none ofDISABLE_TELEMETRY/DO_NOT_TRACK/CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC/DISABLE_GROWTHBOOKset.Control: a terminal session on the same 2.1.260 binary (
%APPDATA%\Claude\claude-code\2.1.260\claude.exe --remote-control --name ...) hasSendMessageand 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 SendMessagefinding above.Impact: a two-machine workflow (Windows desktop app + Linux
--remote-controlCLI) 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
SendMessagein desktop sessions, or extendmcp__ccd_session_mgmt__send_messageto accept Remote Control peer addresses (thebridge:session_.../ ListAgents names) and subagent ids.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
--permission-mode bypassPermissionsSame command line as reported here, verified in
/proc/<pid>/cmdlineof both sessions:No deny rule in user, project or local settings (
grep -r disallowoversettings.json/settings.local.json: no match), andToolSearch select:SendMessagereturns 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 thesession_idfromlist_sessions. The target was verified independently (/proc/<pid>/cwd), andListAgentslists the peer correctly, so discovery and addressing are not the problem.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/enqueueentry with its verbatim content, e.g.: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 insidetool_resultblocks, 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
SendMessagetool 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:
SendMessagestays 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).
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.asarcontainscli_native_send_messageand the samexu="SendMessage",Su=["ListAgents","ListPeers"]block as the installed 1.46388.2. So the coming relaunch will not restore the tool.SendMessagecalls 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'sclaudeprocess came back five seconds later with--disallowedTools SendMessage.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_taskand message those withmcp__ccd_session_mgmt__send_message.