fix: forward pending timeout snapshot in waitForAgentJob fallback timer#89367
Conversation
|
Codex review: stale review; fresh review needed. Summary Next step |
1a08775 to
7724129
Compare
|
Addressed P1 finding: the outer timer now only forwards pending timeout snapshots that carry an endedAt field (hard timeouts). Transient aborts without endedAt remain correctable via the retry grace. Added regression test 'does not return a transient abort snapshot before the timeout grace expires'. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Changed gate from endedAt to timeoutPhase (provider-level hard-timeout attribution). Aborts with endedAt but without timeoutPhase remain correctable. Added regression: abort with endedAt recovers via later start+success before grace expires. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Replaced ad-hoc timeoutPhase string check with shared isHardAgentRunTimeoutPhase() predicate (only preflight / provider / post_turn are hard). Soft phases like queue and gateway_draining remain correctable. Updated regression test to use timeoutPhase: "queue". @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
🦞🧹 I asked ClawSweeper to review this item again. |
ff7fe92 to
90313a1
Compare
|
Rebased on latest main (conflicts resolved, 163/163 tests pass). Real behavior proof added — standalone Evidence after fix: Negative control (main code, same script): Without the fix, hard-timeout snapshots are silently dropped (result=null). With the fix, they are correctly forwarded. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
90313a1 to
b806399
Compare
b806399 to
91e34b7
Compare
|
Land-ready maintainer pass complete for the wait-layer slice of #89095.
|
…b fallback timer (fixes openclaw#89095)
…ntract Also treat provider-started timeout snapshots as hard timeouts in the waitForAgentJob fallback, mirroring buildAgentRunTerminalOutcome. Co-Authored-By: Claude Opus 4.7 <[email protected]>
|
Merged via squash.
|
What Problem This Solves
Fixes an issue where sub-agent runs killed by their hard timeout would record
status: timeoutin the registry but the waiting parent session never received the notification. The parent'sagent.waitouter fallback timer only checkedgetPendingAgentRunErrorand skippedgetPendingAgentRunTimeout, so it resolvednullandagent.waitdowngraded the outcome to a generic soft/gateway_drainingtimeout, losing the hard-phase context.Why This Change Was Made
The
waitForAgentJobouter fallback timer now also forwards a pending hard-timeout snapshot — but it does not rederive the hard/soft decision locally. It reuses the existingterminalOutcomeFromSnapshot()helper (which runs the sharedbuildAgentRunTerminalOutcomeclassifier) and forwards only whenreason === "hard_timeout".This honors the single canonical contract in
src/agents/agent-run-terminal-outcome.ts(per the root rule: "Agent run terminal state: normalize/merge viaagent-run-terminal-outcome.ts; do not rederive timeout/cancel precedence in projections"). In particular the canonical classifier excludes:stopReason === "restart") even when they reached provider-started with a timeout status — these arecancelled, not hard timeouts, and must stay correctable via retry grace;queue/gateway_drainingphases.An earlier revision hand-rolled the gate as
isHardAgentRunTimeoutPhase(phase) || (status === "timeout" && providerStarted === true), which dropped the!restartCancelledguard and would forward a restart-cancelled snapshot as a false hard timeout. Reusing the shared classifier fixes that and shrinks the diff:agent-run-terminal-outcome.tsis now unchanged frommain, and the production change is confined tosrc/gateway/server-methods/agent-job.ts(+17/-1).User Impact
Parent sessions correctly receive hard-timeout completion events for timed-out sub-agents (instead of a generic
gateway_drainingtimeout), while restart-cancelled and soft-phase runs are left to retry grace instead of being mislabeled as hard timeouts.Evidence
All proofs re-run on head
9c074e57180(Node v22.22.0, Linux).1. E2E — real
waitForAgentJob+ realemitAgentEvent, incl. restart-cancelled regressionThe 4 cases drive the real in-process agent event bus and the real
waitForAgentJob: the three hard-timeout phases (preflight/provider/post_turn) forward the precise snapshot, and a new case asserts a restart-cancelled provider-started timeout is NOT forwarded as a hard timeout.2. Negative control — the regression test guards the exact bug
Reverting only the production change in
agent-job.tsto the earlier hand-rolled gate (tests kept) fails exactly the new case, proving it guards the!restartCancelledcontract:3. Contract + server-methods lanes
Not tested here
A live external Gateway with a real model/tool turn was not captured (no live model in this environment). This change is confined to the wait-layer snapshot-forwarding decision, which now defers entirely to the shared
buildAgentRunTerminalOutcomeclassifier; the canonical classifier and its restart-cancelled / soft-phase behavior are covered by the contract tests above plus the real-event-bus e2e and its negative control.Related: #89095 (this PR covers the wait-layer fallback slice; the broader no-deliverable-payload parent-wake path is tracked separately).