fix(subagents): require assistant ack for completion wake#92934
fix(subagents): require assistant ack for completion wake#92934mushuiyu886 wants to merge 3 commits into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 14, 2026, 2:08 PM ET / 18:08 UTC. Summary PR surface: Source +53, Tests +102. Total +155 across 5 files. Reproducibility: yes. at source level: current main resolves the active queue wait at the matching queued user 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 assistant-ack handoff if maintainers accept assistant response events as the completion-consumption boundary; otherwise revise toward teardown recovery or a durable adoption marker. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main resolves the active queue wait at the matching queued user Is this the best way to solve the issue? Yes, conditionally: the PR fixes the implicated owner path without changing non-completion steering, but the assistant-response adoption signal still needs maintainer acceptance. If that signal is rejected, teardown recovery or a durable adoption marker is the safer direction. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 81924cfd5e61. Label changesLabel justifications:
Evidence reviewedPR surface: Source +53, Tests +102. Total +155 across 5 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
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
vincentkoc
left a comment
There was a problem hiding this comment.
Deep review found two replay/acknowledgement blockers in src/agents/embedded-agent-runner/run/attempt.queue-message.ts:
- Any assistant start/end event currently satisfies acknowledgement, including synthetic error/aborted messages, so failed delivery can be reported as consumed.
- After the completion is committed, a timeout still returns
runtime_rejected; the fallback then replays the same completion while the original run may still finish, duplicating responses or side effects.
Best fix: require a successful non-aborted acknowledgement, and model post-commit pending delivery as an irreversible distinct outcome or abort/drain the original run before any replay.
|
Closing because the reported completion-delivery bug is valid, but this acknowledgement design is not safe to land and needs a lifecycle-contract rewrite rather than incremental patching. Three blockers remain:
The safe shape is a closed delivery outcome such as Keeping #92433 open for a canonical lifecycle fix. |
Summary
message_endas delivered even when the requester ended before consuming it; the direct-failure fallback steering path also used transcript commit alone.waitForAssistantResponseAfterTranscriptCommitto the embedded queue wait path and enabled it only for completion handoff steering.Real behavior proof
queued completion user message_end -> requester agent_end -> no assistant responseandcompletion direct send fails -> fallback steering; both now require requester assistant acknowledgement before reporting a steered completion as delivered./media/vdc/code/ai/aispace/openclaw-worktrees/issue-92433, branchfeat/issue-92433, headef5a2f8fd7daedac01fd5e93eecab43111d57b81.testing.setDepsForTest(). It registers a requester in the productionACTIVE_EMBEDDED_RUNSregistry, lets the default productiondeliverAgentHarnessTaskCompletion()path resolve requester activity, and letsqueueEmbeddedAgentMessageWithOutcomeAsync()call the real active-run handle. The handle uses the productionsteerAndWaitForTranscriptCommit()wait logic, so queue/gateway outcomes are not injected.{ "proof": "subagent-completion-active-run-queue-ack", "head": "ef5a2f8fd7daedac01fd5e93eecab43111d57b81", "runtime": "node --import tsx; production deliverAgentHarnessTaskCompletion; production ACTIVE_EMBEDDED_RUNS registry; production queueEmbeddedAgentMessageWithOutcomeAsync; production steerAndWaitForTranscriptCommit; no testing.setDepsForTest and no injected queue/gateway outcome", "ackScenario": { "delivery": { "delivered": true, "path": "steered", "phases": [ { "phase": "direct-primary", "delivered": true, "path": "steered" } ] } }, "teardownScenario": { "queueOutcome": { "queued": false, "sessionId": "active-run-proof-teardown-session", "reason": "runtime_rejected", "gatewayHealth": "live", "errorMessage": "active session ended before queued steering message was consumed" } }, "assertions": { "completionDeliveredAfterAssistantAck": true, "activeQueueUsedAssistantAckOption": true, "teardownBeforeAssistantDoesNotReportDelivered": true, "teardownFailureMentionsUnconsumed": true, "noInjectedQueueOrGatewayOutcome": true }, "events": [ { "event": "active-run-registered", "mode": "assistant-ack", "resolvedSessionId": "active-run-proof-ack-session", "isActive": true }, { "event": "queue-handle-called", "mode": "assistant-ack", "waitForTranscriptCommit": true, "waitForAssistantResponseAfterTranscriptCommit": true }, { "event": "queued-user-message-end", "mode": "assistant-ack" }, { "event": "assistant-message-start", "mode": "assistant-ack" }, { "event": "active-run-registered", "mode": "teardown-before-assistant", "resolvedSessionId": "active-run-proof-teardown-session", "isActive": true }, { "event": "queue-handle-called", "mode": "teardown-before-assistant", "waitForTranscriptCommit": true, "waitForAssistantResponseAfterTranscriptCommit": true }, { "event": "queued-user-message-end", "mode": "teardown-before-assistant" }, { "event": "agent-end-before-assistant", "mode": "teardown-before-assistant" } ] }runtime_rejectedwithactive session ended before queued steering message was consumedwhen the requester reachesagent_endafter the queued completion but before any assistant response, so transcript durability alone is no longer treated as completion delivery. Ordinary non-completion steering still uses transcript-commit delivery.Regression Test Plan
src/agents/embedded-agent-runner/run/attempt.queue-message.test.ts,src/agents/subagent-announce-delivery.test.ts,src/agents/subagent-announce-dispatch.test.tsqueued completion user message_end -> requester agent_end -> no assistant responserejects the active completion wake as unconsumed, and completion direct-send failure fallback steering also waits for requester assistant acknowledgement.Merge risk
clawsweeper:no-new-fix-pr,clawsweeper:needs-maintainer-review,clawsweeper:needs-product-decision, nonlocal channel/live proof boundary.message_start/message_endis the intended completion-consumption boundary. The risk is contained because non-completion steering keeps its previous transcript-commit behavior and unsupported transcript-wait targets still fall back to best-effort wake options.Root Cause
waitForTranscriptCommitas delivery proof for subagent completion handoffs. That proves the internal completion message was committed as a queued user entry, but it does not prove the requester consumed the completion or produced a visible assistant response.message_endproves durability; a later assistantmessage_start/message_endproves requester consumption. If terminal requester state arrives first, the wake is unconfirmed and can fall through to existing fallback delivery.