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

Abierto
#91,419 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:agents area:hooks bug has repro platform:windows
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

## 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.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
api, cli
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
45/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.