Skip to content

[Bug]: same-run retry inherits attempt 1's clean-turn flag, classifying a crashed retry as succeeded #5340

Description

@tomsen02

What happened?

A same-run retry reuses a single run object across attempts. When attempt 1 finishes a clean but empty turn and then stalls (a retryable no-output timeout), and the retried attempt 2 then crashes (non-zero exit, no output), the run is finalized as succeeded instead of failed.

The cause is a stale flag. run.turnCompletedCleanly is set by a clean turn_end (applyClaudeStreamJsonRunBookkeeping) and is read by the terminal classifier (classifyChatRunCloseStatus) as a success override. The same-run retry teardown (tearDownAttemptForRetry in apps/daemon/src/server.ts) resets the other per-attempt fields (child, exitCode, signal, error, stdinOpen) before re-spawning, but leaves turnCompletedCleanly set — so attempt 1's clean-turn verdict vouches for attempt 2's crash.

User-visible impact: a run that actually failed is reported as succeeded (blank/failed result shown as success), and run_retry_finished reports retry_result: success, polluting retry telemetry.

Steps to reproduce

Reproduced end-to-end against a real daemon over the production HTTP API, with a fake claude CLI standing in for the external agent (its stdout is byte-for-byte the real claude-stream-json shape):

  1. Point the daemon at a fake claude binary that, on its first spawn, emits a clean-but-empty turn ({"type":"assistant","message":{"content":[],"stop_reason":"end_turn"}}) then hangs; on its second spawn, exits 1 immediately with no output.
  2. Start a run (POST /api/runs). Attempt 1 sets turnCompletedCleanly = true, then the inactivity watchdog fails it as a retryable no-output timeout → same-run retry.
  3. Attempt 2 crashes (exit 1, no output).
  4. Poll GET /api/runs/:id.

Observed: status: "succeeded", exitCode: 16/6 runs on a live daemon.
Event chain: start → turn_end → error(watchdog) → run_retry_attempted → start → run_retry_finished → succeeded.

Expected behavior

The run finalizes as failed (attempt 2 crashed with a non-zero exit and produced no output). Attempt 1's clean-turn flag must not carry into the classification of a later, independent attempt.

Open Design version

0.14.1

Platform

macOS (Apple Silicon)

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions