fix(reply): retry stalled compacted direct sessions after reset#97099
fix(reply): retry stalled compacted direct sessions after reset#97099849261680 wants to merge 7 commits into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 7:00 PM ET / 23:00 UTC. Summary PR surface: Source +289, Tests +310. Total +599 across 8 files. Reproducibility: yes. at source level: the linked issue has installed-Gateway incident logs, and current main has the abort/error path without a same-turn reset/retry branch. I did not replay the live multi-minute Telegram wedge in this read-only review. Review metrics: 1 noteworthy metric.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow reply-runner recovery after maintainers accept the replay and repair-record contract, with optional Telegram-visible proof confirming one recovered reply and no duplicate generic failure text. Do we have a high-confidence way to reproduce the issue? Yes, at source level: the linked issue has installed-Gateway incident logs, and current main has the abort/error path without a same-turn reset/retry branch. I did not replay the live multi-minute Telegram wedge in this read-only review. Is this the best way to solve the issue? Mostly yes: the reply runner is the right owner boundary, the previous accessor bypass is fixed, and focused tests cover the dangerous guards. The remaining question is maintainer acceptance of the replay and audit semantics, not a discrete code defect I can point to in the latest head. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against aca905cce50a. Label changesLabel justifications:
Evidence reviewedPR surface: Source +289, Tests +310. Total +599 across 8 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 The previous ClawSweeper run reported a Codex infrastructure timeout rather than a patch-quality verdict. Please rerun on current head |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Source/duplicate-lane review note, since the fresh ClawSweeper run timed out rather than producing a patch verdict. I checked this against the closed predecessor PR #96512 and the current head I did not find a new source-level blocker in the reset guard itself. The duplicate-delivery guard is still the key merge risk: the current code gates on compacted direct sessions, blocked+aborted runs, no fallback attempts, no pending tools, no non-error visible payload, then flushes pending block delivery before deciding whether side-effect delivery happened (guard, pre-decision flush, pipeline send-chain/pending tracking). That matches the earlier review shape from #96512. So I think the old #96512 maintainer gates still carry forward rather than being resolved by the re-opened lane: redacted live Telegram or equivalent provider proof should show exactly one recovered reply and no duplicate generic failure text, and maintainers should explicitly accept the one-shot hot-session reset/retry + diagnostic/repair-record semantics for #90516. The current patch emits |
|
@clawsweeper re-review Updated head: The PR body now includes a fresh real Gateway/direct-chat proof for #90516: first local OpenAI Responses SSE request stalls silently, diagnostic recovery closes it, persisted compacted direct session rotates from |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review Updated head: I fixed the CI feedback from the previous head: the retry path no longer imports/calls legacy |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
Closing due to inactivity. |
What Problem This Solves
Fixes #90516.
Long-lived direct chat sessions with compaction history can wedge during an embedded model call. OpenClaw already emits blocked lifecycle diagnostics, but the real Gateway path can surface the diagnostic abort as
FailoverError(reason=timeout, rawError=terminated)and markagent.waitas failed before the outer reset/retry guard gets a chance to rotate the hot session.Why This Change Was Made
This keeps the recovery narrow: compacted direct sessions only, no visible output, no pending tool work, no fallback attempt, no prior reset, and no side-effect delivery evidence. When those guards match, the execution layer defers the terminal failure to the reply runner, the reply runner rotates the hot session once through the session lifecycle accessor, and retries the same inbound turn without duplicating user-message persistence.
The lifecycle backstop now exposes deferred terminal metadata so the execution layer can recognize the real diagnostic shape (
livenessState: blockedplustimeout/terminated) without guessing from a generic timeout string. The reset helper no longer requires callers to expose a mutable whole-session store; if Gateway supplies only the active session entry, persistence still stays centralized inpersistSessionResetLifecycle.User Impact
A Telegram/WebChat/direct-chat user whose compacted session stalls should receive the recovered assistant reply after one automatic reset/retry instead of immediately seeing
LLM request timed out.orLLM request failed.. Non-direct chats, non-compacted sessions, attempts with visible output, fallback attempts, pending tools, and pending block delivery keep the existing failure behavior.Real behavior proof
Behavior addressed: A compacted direct session whose first embedded OpenAI Responses stream stalls silently is reset and retried once instead of surfacing the first run's timeout/error to the user.
Real environment tested: Local macOS source checkout, Node 24.15.0, branch
fix/90516-stalled-session-reset-retryata8442e4571b805f64bf7bdbfe46ce8304c4233e0. No live provider key or channel credential was used; the provider endpoint was a local OpenAI Responses SSE mock behind the real Gateway process.Exact steps or command run after this patch:
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=300000 OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/auto-reply/reply/agent-runner-execution.test.tsOPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=300000 OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/auto-reply/reply/agent-runner.misc.runreplyagent.test.tspnpm exec oxfmt --check --threads=1 src/auto-reply/reply/agent-lifecycle-terminal.ts src/auto-reply/reply/agent-runner-execution.ts src/auto-reply/reply/agent-runner.ts src/auto-reply/reply/agent-runner-session-reset.ts src/auto-reply/reply/agent-runner-execution.test.ts src/auto-reply/reply/agent-runner.misc.runreplyagent.test.tspnpm run lint:tmp:session-accessor-boundarypnpm exec oxlint src/auto-reply/reply/agent-runner-execution.ts src/auto-reply/reply/agent-runner.ts src/auto-reply/reply/agent-runner-session-reset.tsgit diff --checkpnpm tsgo:core:test.agents/skills/autoreview/scripts/autoreview --mode localopenclaw gatewayviacreateOpenClawTestInstance, configure diagnostics withstuckSessionWarnMs=1000andstuckSessionAbortMs=1000, prewrite session store keyagent:qa:webchat:direct:proof-90516withsessionId=stalled-session,chatType=direct,channel=webchat,compactionCount=2,systemSent=true, andtotalTokens=1000; pointopenai/gpt-5.5at a local/v1/responsesmock whose first SSE response never emits model output and whose second SSE response returnsRESET_RETRY_OK; sendchat.sendover Gateway and wait withagent.wait.Evidence after fix: The real Gateway proof returned
chat.send -> {"runId":"proof-90516-run","status":"started"}andagent.wait -> {"runId":"proof-90516-run","status":"ok"}. The local provider mock received exactly 2/v1/responsesrequests; the first silent SSE request was closed by diagnostic recovery, and the second returnedRESET_RETRY_OK. The persisted session store changed fromstalled-sessiontofd5c357b-5533-46d3-b436-713fff9de9c7, withsystemSent:false,totalTokens:10, andcompactionCount:2. Agent lifecycle events showedfinishingwitherror:"LLM request timed out.",aborted:false,livenessState:"blocked"; then syntheticendwithaborted:true,livenessState:"blocked"; thenauto_session_reset_retryattempting; then the second run emittedRESET_RETRY_OK; thenauto_session_reset_retrycompletedwithpreviousSessionId:"stalled-session"and the new session id.Observed result after fix: The user-visible run completed successfully after one reset/retry, no generic timeout payload was delivered as the final outcome, and the proof stderr did not contain the temporary
RUN_THROW_RESET_DEBUGmarker.What was not tested: This proof does not use real Telegram credentials or a live OpenAI key. The covered behavior is the shared Gateway/direct-chat/reply-runner path with a local OpenAI Responses-compatible SSE provider mock.