fix(auto-reply): clear pending-final state before honoring post-send abort (#89115)#93201
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 15, 2026, 3:39 AM ET / 07:39 UTC. Summary PR surface: Source +5, Tests +50. Total +55 across 2 files. Reproducibility: yes. at source level: current main can send a final reply and then check the abort signal before clearing pending-final state. I did not run a live gateway timing repro in this read-only review. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow cleanup-before-abort fix once the draft is maintainer-ready and focused dispatch/session gates are green; keep the linked bug open until the PR merges. Do we have a high-confidence way to reproduce the issue? Yes, at source level: current main can send a final reply and then check the abort signal before clearing pending-final state. I did not run a live gateway timing repro in this read-only review. Is this the best way to solve the issue? Yes; clearing durable pending-final state after accepted final delivery and then surfacing the abort is the narrow owner-path fix. Earlier closed branches were either less complete or bundled broader dispatcher changes. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 7a7165ad22c4. Label changesLabel justifications:
Evidence reviewedPR surface: Source +5, Tests +50. Total +55 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
…abort (#89115) In dispatchReplyFromConfig the user-message success branch ran throwIfDispatchOperationAborted() *before* clearPendingFinalDeliveryAfterSuccess(). If stuck-session recovery aborted the run in the window between the final reply shipping and the clear, the message was delivered but pendingFinalDelivery stayed true forever — the get-reply redelivery short-circuit then silently blocked every future inbound and the agent "went silent" (#89115). Reorder so the durable pending-final bookkeeping is cleared first, then honor the abort afterwards (preserving abort reporting). Also clear the stranded pendingFinalDeliveryIntentId field — agent-command.ts already clears it but the success helper did not. Adds a regression test that fails on main (clear is skipped, state stranded) and passes with the fix. Supersedes #90229 and #89130.
31faa7f to
14bdcd5
Compare
…abort (openclaw#89115) (openclaw#93201) In dispatchReplyFromConfig the user-message success branch ran throwIfDispatchOperationAborted() *before* clearPendingFinalDeliveryAfterSuccess(). If stuck-session recovery aborted the run in the window between the final reply shipping and the clear, the message was delivered but pendingFinalDelivery stayed true forever — the get-reply redelivery short-circuit then silently blocked every future inbound and the agent "went silent" (openclaw#89115). Reorder so the durable pending-final bookkeeping is cleared first, then honor the abort afterwards (preserving abort reporting). Also clear the stranded pendingFinalDeliveryIntentId field — agent-command.ts already clears it but the success helper did not.
…abort (openclaw#89115) (openclaw#93201) In dispatchReplyFromConfig the user-message success branch ran throwIfDispatchOperationAborted() *before* clearPendingFinalDeliveryAfterSuccess(). If stuck-session recovery aborted the run in the window between the final reply shipping and the clear, the message was delivered but pendingFinalDelivery stayed true forever — the get-reply redelivery short-circuit then silently blocked every future inbound and the agent "went silent" (openclaw#89115). Reorder so the durable pending-final bookkeeping is cleared first, then honor the abort afterwards (preserving abort reporting). Also clear the stranded pendingFinalDeliveryIntentId field — agent-command.ts already clears it but the success helper did not.
…abort (openclaw#89115) (openclaw#93201) In dispatchReplyFromConfig the user-message success branch ran throwIfDispatchOperationAborted() *before* clearPendingFinalDeliveryAfterSuccess(). If stuck-session recovery aborted the run in the window between the final reply shipping and the clear, the message was delivered but pendingFinalDelivery stayed true forever — the get-reply redelivery short-circuit then silently blocked every future inbound and the agent "went silent" (openclaw#89115). Reorder so the durable pending-final bookkeeping is cleared first, then honor the abort afterwards (preserving abort reporting). Also clear the stranded pendingFinalDeliveryIntentId field — agent-command.ts already clears it but the success helper did not.
Summary
Fixes #89115 — agent "goes silent" after a delivered final reply.
In
dispatchReplyFromConfig, the user-message success branch calledthrowIfDispatchOperationAborted()beforeclearPendingFinalDeliveryAfterSuccess():If stuck-session recovery aborts the embedded run in the narrow window between
the final reply shipping and the clear, the message is delivered but
pendingFinalDeliverystaystrueforever. The get-reply redeliveryshort-circuit then keys off that flag and silently blocks every future inbound —
the agent appears to go silent until the session is manually reset.
The fix
the abort. The reply already shipped, so the clear must run; the abort is still
surfaced afterwards (the dispatch routes through
finishReplyOperationAbortedDispatch()and reports
queuedFinal: false), so cancellation reporting is preserved.pendingFinalDeliveryIntentIdfield.
agent-command.tsalready clears it on its path; the success helperdid not, leaving stale state behind.
This is intentionally narrow: it does not change delivery-success detection,
add idle-wait calls, or touch the dispatcher outcome counts.
Why this supersedes #90229 and #89130
Both prior PRs had the right instinct; this one keeps the correct parts and drops
the risk:
dropping the post-delivery cancellation reporting the codebase expects; it also
missed
pendingFinalDeliveryIntentIdand its test was mock-only.intentIdcleanup, but bundledunrelated changes onto a P1 hot path (
waitForReplyDispatcherIdle, acompletedFinalDeliveryAttempt/final-outcome-counts rework whose synchronousfailed-count check is unreliable for async dispatchers) and had drifted from
main(thesendFinalPayloadtranscript-mirror helpers were renamed), so itno longer applied cleanly.
Real behavior proof
pendingFinalDelivery: true, silently blocking subsequent inbound messages.node scripts/run-vitest.mjs src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.tsclears pending final delivery when abort fires after a successful final send (#89115)drives the real
dispatchReplyFromConfigcontrol flow (abort fired from insidethe dispatcher's
sendFinalReply, in the post-send window). Result: the clearruns, every
pendingFinalDelivery*field (includingpendingFinalDeliveryIntentId)is cleared, and the run is still surfaced as aborted (
queuedFinal: false).sessions.test.ts(44) andagent-command.live-model-switch.test.ts(62) pass.updateSessionStoreEntryis called 0 times (the abort threw before theclear), i.e. the pending state is stranded exactly as reported.
and real stuck-session recovery timing.
deterministic regression test (fails on
main, passes with the fix) plustypecheck/lint/format; no live multi-process gateway run was performed in this
environment.
Tests and validation
Commands run:
node scripts/run-vitest.mjs src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.ts— 7/7node scripts/run-vitest.mjs src/config/sessions/sessions.test.ts src/agents/agent-command.live-model-switch.test.ts— 44 + 62pnpm tsgo:coreandpnpm tsgo:test:src— cleanoxfmt --check+oxlinton the changed files — cleanRegression coverage added: a test that fails before the fix (clear skipped,
state stranded) and passes after.
Risk checklist
longer strands the session; subsequent inbound messages are processed.