anthropics / anthropics/claude-code
[BUG] Backgrounded/parked tool calls are still reported as "The user doesn't want to take this action right now" — the honest message already exists on the neighbouring branch (re-file of #78288)
- Lingua principale
- Python
- Stelle
- 145k
- Fork
- 23.1k
- Metriche di merge delle PR
- Metriche PR in attesa
Descrizione
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
### What's Wrong?
Re-filing #78288, which was auto-closed as inactive without a human reply. The defect is still present in the versions I run today (CLI 2.1.251; remote ccd-cli 2.1.255 / 2.1.258 / 2.1.260), and there is a new detail that makes it cheaper to fix than when I first reported it: **the codebase now contains the honest message this report asked for — it is just wired to the neighbouring branch.**
When a tool call aborts because its session was backgrounded — a parked subagent, or the main session losing its channel to a second `ccd-cli` process serving the same session id — the harness renders it as a human refusal:
> The user doesn't want to take this action right now. STOP what you are doing and wait for the user to tell you how to proceed.
No user made any decision and no permission rule was involved (the session was in `bypassPermissions` throughout, and `Read`/`Grep`/`Glob` are allowed in our managed settings).
The leaf agent cannot tell this apart from a real denial, so it concludes the file or material is **absent** and reports that as a finding. A misattributed refusal is strictly worse than a crash: the message actively instructs the model to stop and wait for a user who was never asked.
### What Should Happen?
An abort caused by infrastructure should never be rendered as a user decision.
Concretely, a two-part fix that reuses machinery already in the codebase:
1. Move `background` and `subagent_park` to the non-user-attributable side of `isUserAttributableAbortKind`, next to `recovery_timeout` and `turn_teardown`.
2. Give the `background` branch of `createSyntheticErrorMessage` a truthful message, in the spirit of the `turn-abort` one that already exists — e.g. "[Tool call did not complete: its session was backgrounded and the call was not adopted within 600 s. Nothing refused it; re-run it if still needed.]". `toolDenialKind: "cancelled"` is also misleading to anything downstream that reads it.
Anything truthful is enough. The leaf agent can then report an infrastructure failure instead of inventing a factual conclusion.
Secondary (unchanged from #78288): fail fast when the agent is already parked instead of paying 600 s per call, and warn or refuse when `--resume=` targets a session whose original process is still alive (related root condition: #19039, closed as duplicate).
### Error Messages/Logs
```shell
// what the leaf agent receives:
The user doesn't want to take this action right now. STOP what you are doing and
wait for the user to tell you how to proceed.
// current bundle - two adjacent branches, one honest and one not:
createSyntheticErrorMessage(e, n, r) {
if (n === "user_interrupted") {
let o = reason(this.toolUseContext.abortController.signal.reason);
if (o === "turn-abort") return { content: TURN_ENDED_MSG, toolDenialKind: "interrupted", ... };
if (o === "background") return { content: REFUSAL_MSG, toolDenialKind: "cancelled", ... };
}
}
TURN_ENDED_MSG = "[Tool call did not complete: the turn was ended to deliver the
message that follows. Nothing refused it; re-run it if still needed.]"
REFUSAL_MSG = "The user doesn't want to take this action right now. STOP ..."
// the classifier already has the correct side; background/subagent_park are on the wrong one:
function isUserAttributableAbortKind(kind) {
switch (kind) {
case "user_cancel": case "remote_cancel": case "shutdown":
case "interrupt": case "background": case "subagent_park": return true;
case "turn_teardown": case "recovery_timeout":
case "server_fallback_tombstone": return false;
}
}
// the 600 s wait is unchanged: literal 600000, now under a renamed symbol.
// identifiers are minified and build-specific; the string literals and the
// control flow are stable and greppable in any install.
```
### Steps to Reproduce
Root condition: one session ends up served by two `ccd-cli` processes.
1. Start a long run (in my case ~240 subagents via the Workflow tool, `spawnDepth: 1`). Permissions resolved through `--permission-prompt-tool stdio`.
2. While process A is still alive and holding that run, start a second process with `--resume=`.
3. Process A keeps executing its agents but has lost its channel. Its subagents are parked.
4. Every tool call from those agents waits exactly 600 s to be adopted, is not adopted, and aborts with `reason: "background"`.
5. The abort is rendered as the human-refusal message above.
Step 2 is not exotic — it is what happens when a session is resumed while its original process has not actually exited.
Measured on the affected run:
- 5 agents affected, 41 tool calls aborted; `tool_use` -> `tool_result` delta of **600.00-600.01 s on every single one** (sigma ~ 0.01 s), across `Read`, `Grep` and `Glob`.
- Not path-dependent: one agent read 115 files fine, then every later call failed — including a 5-line read of a small file in the project root.
- Not subagent-specific: the main session was hit too (an `Edit`, same message, 600 s to the second).
- In-process tools unaffected: `StructuredOutput` returned in 0.02-0.06 s throughout, including inside the broken agents. Only calls crossing the channel hung.
- Clean control: the same workflow relaunched from the process that still held its channel ran ~155 agents with zero occurrences.
### Claude Model
Opus
### Is this a regression?
No, this never worked
### Last Working Version
_No response_
### Claude Code Version
2.1.251 (Claude Code); remote ccd-cli 2.1.255 / 2.1.258 / 2.1.260
### Platform
Anthropic API
### Operating System
Ubuntu/Debian Linux
### Terminal/Shell
Other
### Additional Information
**Why I re-filed rather than commented.** #78288 was closed by the stale bot on 2026-09-04 with "please open a new issue if this is still relevant". It is: I verified the classifier, the mapping and the 600 s constant against the binaries I run today before writing this.
**Frequency, honestly stated.** I swept 50 days of my own transcripts with a detector that pairs `tool_use`/`tool_result` and keeps only deltas >= 570 s. 102 transcripts contain the refusal string; 95 of those are genuine denials (a rule, a hook, or the user), and the remaining 7 are all the same July incident. So this is rare — but when it hits a long run, the run is unusable, because the agent's "this file does not exist" conclusions are indistinguishable from real ones without going back to the raw transcript and measuring timings.
**On sharing evidence.** The raw workflow journal and transcripts contain third-party client data and cannot be anonymised without ceasing to be the evidence. The derived measurement is where the signal is (600.00 s across 41 independent calls, three tools, two processes), and the bundle strings above are verifiable in any install without my data.
**Ruled out on my side:** PreToolUse hooks are not involved — our load-protocol hook exits 0 for read tools before any check, and its own deny carries a different message. Permission mode never fell back to `ask` (11/11 transcript samples, including the resume).
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
Start by grepping the codebase for createSyntheticErrorMessage, isUserAttributableAbortKind, TURN_ENDED_MSG, REFUSAL_MSG, and the literal 600000. The issue asks for background and subagent_park aborts to stop being treated as user-attributable, and for background synthetic errors to use a truthful non-refusal message. Done means those aborts no longer render as a user refusal and any relevant tests or CLI checks cover the mapping.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- cli, linux, python
- Ambito
- backend, cli
- Tipo di issue
- Bug
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Stato di attività
- Attiva
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 62/100