anthropics / anthropics/claude-code
[BUG] StopFailure fires for internal helper queries and subagent failures on the parent session; under low-priority mode every excluded helper 429 reports a failed turn that never happened
- Vorherrschende Sprache
- Python
- Sterne
- 145k
- Forks
- 23.1k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
## Summary
`StopFailure` fires for API errors in Claude Code's own internal helper queries (prompt suggestion, away summary, memory extraction, agent summary) and for background subagent failures, always with the parent `session_id`. Under low-priority mode those helpers are excluded from retry, so while a usage limit is exhausted each one dies on a 429 and fires `StopFailure` at an idle prompt. Any hook consumer that reads the event as "the turn ended on an API error" (its documented meaning) shows a failed turn that never happened.
## Environment
- Claude Code 2.1.258, native binary, Windows 11
- Interactive REPL, `bypassPermissions`, `/low-priority` enabled after hitting the 5-hour limit
- `StopFailure` hook wired to a small command hook that forwards the payload to a desktop notifier
## What happened
1. Main loop launched nine background agents and parked ("waiting on 9 agents", `Stop` with a non-empty `background_tasks`). Seven agents died on `429 rate_limit`; each death fired `StopFailure` on the parent session, and each failed task-notification woke the main loop, which hit the same 429 and fired `StopFailure` again. Fourteen `StopFailure` hooks in about a minute for one underlying limit.
2. Enabled `/low-priority`. The main loop and agents resumed correctly.
3. Two minutes later, with the main loop still parked and no user input, three more `StopFailure` hooks fired (`error: "rate_limit"`, no `error_details`) about 20-30 s apart. No transcript line, no subagent transcript line, and no task output records any API error in that window. The next entry in the session transcript is a completed-agent task-notification six minutes later.
## Why (from the 2.1.258 bundle)
- The forked helper queries all go through the same query generator as the main turn: the `Gv` fork helper (`querySource` in `prompt_suggestion`, `away_summary`, `extract_memories`, `agent_summary`, `skipTranscript: true`) iterates `FO`, which runs the main loop generator (`o1n`, both branches).
- That generator's API-error exit calls the `StopFailure` emitter (`fOe`) for any `isApiErrorMessage`, using `session.id`. The only skip is a delegated-observation subagent context. Nothing distinguishes a fork, a subagent, or the main turn in the payload; the schema is base fields plus `error`, `error_details`, `last_assistant_message`.
- The low-priority retry allow-list (`JZ`: `agent:*`, `compact`, `hook_agent`, `hook_prompt`, `side_question`, `web_search_tool`, `web_fetch_apply`, `repl_sampling`, `auto_mode*`, `chrome_mcp`, `artifact_comment_reply`, plus the builtin agent keys) excludes those helpers. For an excluded source the 429 handler returns null, so the request errors instead of waiting.
- Result: with the limit exhausted, every helper fork fails immediately and fires `StopFailure`, while the real turn is fine.
Which helper fired in step 3 is inferred (the payload carries nothing to tell, and forks skip the transcript); the timing matches returning to an idle prompt.
## Expected
`StopFailure` means the turn ended on an API error. Helper forks and background subagent deaths are not the turn ending. The docs describe it that way and consumers (auto-continue hooks, dashboards, notifiers) depend on it.
## Suggested fix
Either of these would do; both would be best:
- Do not fire `StopFailure` from `skipTranscript` forks, or more generally from non-main `querySource`s. A subagent death already surfaces through its failed task-notification and the re-woken main loop's own outcome.
- Add `query_source` (and `agent_id` when present) to the `StopFailure` payload so consumers can filter, the way `SubagentStop` already carries `agent_id`.
Separately, the pre-low-priority cascade in step 1 is worth a look: each dead agent re-wakes a main loop that cannot succeed, so one exhausted limit produces `2N` `StopFailure` hooks.
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Rechercherichtung
Start by locating the StopFailure emitter (fOe), fork helper (Gv), main loop generator (o1n), and low-priority retry allow-list (JZ) in the 2.1.258 bundle. Reproduce the idle-prompt scenario with /low-priority, background agents, and a StopFailure command hook. Done means helper forks and background subagent failures no longer create misleading parent StopFailure events, with query_source and agent_id included if that option is chosen.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- api, cli
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 45/100