subagent: make a missed continue the tool call's own error
`continue` returned "started" the instant `Claude::spawn` handed back a pid, and a resume that matched nothing only surfaced later, as an end-of-turn todo. By then the caller had moved on believing it had a running subagent. A pid is proof enough for `start`, which creates its session: the spawn succeeding is the whole story. It is not proof for a resume — claude exits non-zero a fraction of a second *after* the process exists. So `continue` now waits for the first real answer and reports a miss as its own `Err`, carrying claude's message and the directory searched. The wait ends on whichever comes first, so a successful `continue` pays no fixed delay: the turn's first non-terminal stream event settles it at about the same moment a miss's exit would have. Measured on this box: 14 runs of the driver's own invocation against a missing session took 550-1087 ms spawn to exit, and a healthy turn's first event lands at roughly 500 ms. The five-second cap is ~4.6x the slowest miss and is only ever reached by a child that neither speaks nor exits. The underway signal reads the event's kind, not its content: a missed resume is not silent — it emits a terminal `result` event and stderr before exiting — so "any sink callback" would have reported every miss as a successful start. Liveness still counts all three callbacks. The end-of-turn todo is unchanged for every failure later in the turn; the only one it no longer repeats is the miss the caller was just handed. Refs #4405
This commit is contained in:
parent
31c76ddf32
commit
6e2de33f26
5 changed files with 385 additions and 52 deletions
|
|
@ -50,9 +50,9 @@ stuck. That one number replaces inferring the same thing from `ps` output
|
|||
and CPU-time deltas.
|
||||
|
||||
Every line the subagent's `claude` process writes bumps the timestamp —
|
||||
stream-json events, plain stdout chatter and stderr alike — and nothing
|
||||
about the content is inspected. The record says the child is alive, not
|
||||
what it's doing. The clock starts at the spawn, so a subagent that wedged
|
||||
stream-json events, plain stdout chatter and stderr alike — and what the
|
||||
subagent actually said is never read. The record says the child is alive,
|
||||
not what it's doing. The clock starts at the spawn, so a subagent that wedged
|
||||
before it ever emitted anything still reports a climbing age rather than
|
||||
no age at all. It's dropped when the turn ends, since a finished turn has
|
||||
no progress left to describe.
|
||||
|
|
@ -65,19 +65,32 @@ starting a fresh session, so the check could only duplicate the lookup
|
|||
the driver was about to do — while answering as though the session were
|
||||
gone. The usual truth is that the session exists somewhere else.
|
||||
|
||||
`continue` returns as soon as the process is confirmed running, same as
|
||||
always, so the failure lands where every other failed turn lands: that
|
||||
turn's end-of-turn todo, carrying claude's own message plus the location
|
||||
this daemon searched.
|
||||
`continue` waits for that answer instead. Where `start` returns the
|
||||
instant the process exists — it creates its session, so the spawn
|
||||
succeeding is the whole story — a resumed turn can fail a moment _after_
|
||||
a pid exists, and a pid is no proof that turn began. `continue` holds the
|
||||
tool call open until the turn is underway or the resume has come back
|
||||
missed, and reports a miss as the call's own error, carrying claude's
|
||||
message plus the location this daemon searched:
|
||||
|
||||
```
|
||||
claude error: no session matched the requested id or title (searched /home/agent/.claude for cwd /home/agent/work; if it was started elsewhere, pass the `dir` it was started in)
|
||||
continue error: claude error: no session matched the requested id or title (searched /home/agent/.claude for cwd /home/agent/work; if it was started elsewhere, pass the `dir` it was started in)
|
||||
```
|
||||
|
||||
The directory is the part claude's own message never names, and the part
|
||||
that resolves the confusion: pass `dir` to point `continue` at the
|
||||
directory the session was started in.
|
||||
|
||||
Nothing here is a fixed delay on the way to a successful turn. The wait
|
||||
ends on whichever comes first — the turn's first stream event or its
|
||||
early exit — and on this box both land inside a second, so a `continue`
|
||||
that works answers about as fast as it did before. A five-second cap
|
||||
bounds the one case neither covers: a child that neither speaks nor
|
||||
exits, reported as started, with the end-of-turn todo left to say how it
|
||||
goes. That todo still carries every failure that happens later in the
|
||||
turn, exactly as before; the only one it no longer repeats is the miss
|
||||
the caller has just been handed to its face.
|
||||
|
||||
## A killed turn
|
||||
|
||||
A subagent whose `claude` process dies on a signal — the kernel's OOM
|
||||
|
|
|
|||
Loading…
Reference in a new issue