Skip to content

wait_for returns 'Agent has already completed' for a working agent — terminal short-circuits read the stale registry record, not live state #473

Description

@EtanHey

Reported live by voiceClaude (VoiceLayer lead) during an M1 deploy, 2026-08-19, v0.4.47

Full report with verbatim tool output: docs.local/reports/2026-08-19-registry-watch-disagreement.md

wait_for(ids:[...], target_state:"idle") returned immediately (elapsed:0, source:"immediate") with matched:false, state:"done", error:"Agent has already completed" — for an agent that was mid-brew install and kept producing output. The same response's own health block contradicted it: reconciled_state:"working", screen_confirmed_state:"working", issue_codes:["registry_screen_disagreement"].

Reporter's words: "A lead that trusts it will report a worker's task finished while the worker is still running. I hit this and had to abandon wait_for entirely."

Mechanism (verified in source)

waitForAgent's terminal short-circuits read the raw registry record:

  • agent-engine.ts:8128 const initial = this.registry.get(agentId)
  • agent-engine.ts:8148 if (initial.state === "error" && ...) → immediate return
  • agent-engine.ts:8160 if (initial.state === "done" && targetState !== "done") → immediate matched:false

F1 (#457/#466) taught caller resolution, delivery gating, and closure to use resolveLiveAgentState; wait_for was not in that lane's scope and still trusts the record. Against the known stale-done registry class (#408) this makes the fleet's primary monitoring primitive return false completion.

Asks

  1. Gate both short-circuits on the live-resolved state (resolveLiveAgentState, already imported at agent-engine.ts:48 and used at :1733) — never terminate a wait on a record state the screen contradicts.
  2. When record and screen disagree, the top-level state must reflect the reconciled value, not the record — a caller reads state first and the honest verdict is currently buried in health.
  3. Regression test with the exact live shape: registry done + screen working + target_state:"idle" ⇒ the call BLOCKS.

— cmuxlayerClaude (lead) · claude-code/claude-fable-5

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions