anthropics / anthropics/claude-code

[BUG] SendMessage returns {"success":true} for messages never delivered; a long-lived session instance goes deaf both ways; stale bridge-pointer leaves host "Connected" with 0 workers (2.1.234)

未关闭
#89,938 2 条评论 1 个 reaction 已指派 0 人 在 GitHub 查看
area:agents bug has repro platform:linux
主要语言
Python
星标
145k
派生
23.1k
PR 合并指标
PR 指标待抓取

描述

> **Amended 2026-08-26.** The original report blamed `--resume` on the sender side. That was wrong; see [the correction comment](https://github.com/anthropics/claude-code/issues/89938#issuecomment-5432701433). This body has been rewritten to describe what the evidence actually supports. Two defects are reported.

## Defect 1 — `SendMessage` returns `{"success":true}` for messages that are never delivered

Cross-session messaging between local Claude Code sessions returns `{"success":true}` with a `msg_id` while the message is silently lost. The recipient's transcript never shows an inbound block, and its context never sees the message, including across many subsequent active turns. Nothing is queued or retried; nothing is logged.

The false success is the core problem: agents believe they have communicated. We only caught it by grepping the recipient's transcript for a unique token after every send.

## Defect 2 — a long-lived session instance can become unreachable in **both** directions, with no error surfaced

One session instance (months old, repeatedly restarted, many context compactions, 16 MB transcript) reached a state where:

- it never delivered a message to anyone, ever — always `success`, never received;
- **and** nothing could be delivered *to* it, from any sender, fresh or not;
- while its unix socket was LISTENing and accepting connections, its registry entry was valid, and it otherwise worked normally for interactive use.

No error was surfaced on either side. Only full recreation of the session cured it — killing and restarting the host process was **not** enough (see "Recovery" below).

## Environment

- Claude Code **2.1.234**, identical binary across all sessions (`~/.local/share/claude/versions/2.1.234`)
- Ubuntu 26.04, kernel 7.0.0-30-generic, x86_64
- Long-lived sessions: `claude remote-control --name --spawn=same-dir --permission-mode bypassPermissions`, one per project dir, each inside tmux
- Registry entries in `~/.claude/sessions/*.json` all show `peerProtocol: 1`, `entrypoint: sdk-cli`, live pids, valid `messagingSocketPath`
- Note: **every** remote-control worker runs with `--resume=`, including freshly created ones — the flag is set unconditionally and does not distinguish session age

## Evidence matrix

Delivery verified by grepping the **recipient's** transcript for a unique nonce.

| Sender ↓ / Recipient → | healthy session A | degraded session B | recreated session B′ |
|---|---|---|---|
| fresh headless `claude -p` | ✅ 4/4, incl. full processing | ❌ | — |
| healthy long-lived session A | — | ❌ | ✅ delivered |
| recreated session B′ | ✅ delivered | — | — |

Every failure has degraded session B as an endpoint; no cell isolates the sender. Historical data point: 50 messages were exchanged successfully between these sessions over two days before B degraded.

## Proof-of-delivery standard (useful for anyone reproducing)

Three JSONL entry types appear in the recipient's transcript, and only the last two prove anything:

| entry type | meaning | proof? |
|---|---|---|
| `queue-operation` | queued (~10 s) | **no** — appears even when nothing is processed |
| `attachment` / `queued_command` carrying the `` block | materialized (~40 s) | yes |
| `assistant` | processed in a turn | yes |

Grepping too early, or not distinguishing these, produces false conclusions in both directions. A second trap: the nonce also appears in the **sender's** own transcript (its tool call), which is not evidence of delivery.

## Transport layer ruled out

- All peer sockets in `/run/user//cc-socks/` are LISTENing and held by live processes (`ss -xlp`), including the failing ones; a raw `connect()` to each succeeds, including B's.
- Registry `procStart` values match `/proc//stat` field 22 — no stale identity.
- No on-disk queue or spool; messages are lost, not deferred.
- Environment differences (extra CA cert var, node compile cache, inherited shell vars) ruled out: injecting the failing session's environment into a fresh `claude -p` still delivers.
- **Connection sampler**: polling `ss -xtp` for the sender's pid every 250 ms during a `SendMessage` call toward B showed the sender **never attempting a connection** to B's unix socket. Under the revised model this is still unexplained and may be the best lead — it suggests the send path can abandon local delivery before connecting, without surfacing an error, based on some resolved state about the recipient.

## Defect 3 (related, likely the same subsystem) — stale `bridge-pointer.json` ⇒ CCR v2 404 ⇒ host runs with no worker

While recovering from Defect 2 we hit a distinct, reproducible failure:

```
[17:41:24] Error: CCR v2 worker registration failed for session cse_XXXX: Request failed with status code 404
·✔︎· Connected · system · HEAD
Capacity: 0/32
```

- `~/.claude/projects//bridge-pointer.json` keeps the previous `sessionId`/`environmentId`. If the previous session died uncleanly and the server reaped it, worker registration 404s.
- The host then stays up advertising **`Connected`** and a QR/pairing URL, with **`Capacity: 0/32`**, **zero child worker process**, no entry in `~/.claude/sessions/`, no socket, and absent from `claude agents --json`. It looks healthy from the terminal and is invisible/dead from the apps.
- **Restarting does not fix it**: the new process rewrites `bridge-pointer.json` updating only `pid`/`procStart` while preserving the stale `sessionId`/`environmentId`, so it 404s again. We burned two restart cycles before finding this.

Suggested fixes: don't report `Connected` when worker registration failed; treat a 404 on registration as a signal to discard the pointer and register a fresh session; surface the condition beyond one startup log line.

## Recovery / workaround

1. Stop the supervisor, kill the tmux session, confirm no `claude remote-control --name ` remains.
2. `mv bridge-pointer.json bridge-pointer.json.stale-` (never delete — you may want the old environment id).
3. Restart. A new `sessionId` **and** `environmentId` are generated, so the `claude.ai/code?environment=…` URL changes; the old one points at a dead environment.
4. This also cured Defect 2 — the recreated instance sends and receives normally.

We now also monitor for Defect 3 automatically: a remote-control host with **no child process** is the fast, reliable health signal; `pgrep` on the host process alone proves nothing.

## Impact

Multi-session/agent coordination degrades silently to "everyone believes they sent, nobody received", and a session can be dead to the mobile/desktop apps while its terminal says `Connected`. The misleading `success` and the misleading `Connected` cost us most of a day.

Happy to provide sanitized transcripts, registry snapshots, or run instrumented builds.

贡献指南

这个仓库没有索引到贡献指南

调研方向

Start by tracing the local cross-session SendMessage path and the remote-control startup path that reads ~/.claude/projects//bridge-pointer.json and writes ~/.claude/sessions/*.json. Reproduce with two remote-control sessions and verify delivery by grepping the recipient transcript for attachment/queued_command or assistant entries, not queue-operation. Done means undelivered sends do not return success, degraded endpoints surface an error, and CCR v2 404 registration does not leave the host showing Connected with 0 workers.

由索引模型根据 Issue 内容生成。

评估

技术栈
linux, python
领域
backend, cli
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
42/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。