fix(gateway): preserve Control UI turns across restarts - #106151
Conversation
49451e0 to
98db2ce
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98db2ceb43
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai | |||
| - **Plugin SDK API baseline:** cover every public entrypoint, preserve complete declaration shapes without source-line churn, and run baseline and export-surface guards from changed-file validation. | |||
| - **Official plugin beta compatibility:** keep the exact beta.5 session-store helper imports working over SQLite through the documented deprecation window, preventing installed Codex and Feishu plugins from failing during package acceptance and upgrades. (#105287) Thanks @vincentkoc. | |||
| - **SQLite terminal session recovery:** track physical transcript mutation time in the agent database so killed or timed-out main sessions rotate when transcript writes outlive the registry update, while preserving legacy transcript mtimes during doctor import. | |||
| - **Control UI restart recovery:** commit eligible user turns and running-session metadata to SQLite before acknowledging them, resume interrupted work through one durable dispatch identity, suppress late outbox duplicates, and show an interruption notice in WebChat history when a turn cannot be resumed. (#95141) Thanks @openperf for the WebChat notice fallback contribution. | |||
There was a problem hiding this comment.
Remove the non-release changelog entry
This change adds a regular fix entry to CHANGELOG.md, but the root AGENTS.md states that CHANGELOG.md is release-only and that release generation owns it. Because the release flow derives changelog entries from merged PRs/direct commits and requires the release SHA delta to be changelog-only, keeping this manual entry in a non-release fix risks duplicate or stale release notes and breaks the release-process invariant; move this context to the commit/PR release-note text and let release generation add the changelog line.
Useful? React with 👍 / 👎.
3f86158 to
c8a2524
Compare
|
Codex review: needs real behavior proof before merge. Reviewed July 13, 2026, 4:48 AM ET / 08:48 UTC. Summary PR surface: Source +1941, Tests +2614, Docs +12, Generated +6, Other +5. Total +4578 across 67 files. Reproducibility: yes. at source level: the linked report describes the restart timing window, and the patch clearly moves acknowledgement behind durable transcript and claim admission. A failing current-main run was not independently established during this review. Review metrics: 3 noteworthy metrics.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge the atomic admission, migration, recovery scan, identity-bound handoff, and terminal non-replay behavior as one coordinated change only after exact-head checks pass and visible proof demonstrates fresh-install, v6-upgrade, resumed-once, and stopped-never-replayed paths. Do we have a high-confidence way to reproduce the issue? Yes at source level: the linked report describes the restart timing window, and the patch clearly moves acknowledgement behind durable transcript and claim admission. A failing current-main run was not independently established during this review. Is this the best way to solve the issue? Yes. Keeping the transcript and recovery claim atomic in the canonical per-agent SQLite database and adopting it through a single-use session-bound handoff is safer than adding a second queue, sidecar, dual-write path, or runtime fallback. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against a857c4430049. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1941, Tests +2614, Docs +12, Generated +6, Other +5. Total +4578 across 67 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
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c8a2524a14
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const dispatchResult = await callGateway<{ runId: string; status?: unknown }>({ | ||
| method: "agent", | ||
| params: agentParams, | ||
| timeoutMs: 10_000, |
There was a problem hiding this comment.
Dispatch recovery calls as backend clients
When restart recovery resumes a durable Control UI turn, this agent RPC includes expectedExistingSessionId, but the call does not set mode: GATEWAY_CLIENT_MODES.BACKEND. callGateway defaults callers to CLI mode (src/gateway/call.ts:1014), and the agent handler rejects expectedExistingSessionId unless the client mode is BACKEND (src/gateway/server-methods/agent-expected-session.ts:29), so every automatic recovery dispatch through this path is rejected before it can resume the accepted turn.
Useful? React with 👍 / 👎.
c8a2524 to
572925c
Compare
|
Merged via squash.
|
) * fix(gateway): preserve Control UI turns across restart * perf(sessions): index restart recovery state * fix(sessions): avoid replaying pre-admitted turns * fix(sessions): index per-entry recovery status * fix(sessions): guard restart failure notices * fix(sessions): preserve terminal claims across admissions * fix(sessions): retry failed recovery notices * fix(sessions): harden restart retry ownership * fix(gateway): tighten restart recovery typing * fix(sessions): close restart recovery races * refactor(sessions): split restart recovery ownership * chore(sessions): clean restart recovery lint * chore: drop release-owned changelog entry * fix(gateway): satisfy restart recovery guards * test(gateway): cover Control UI durable admission
Related: #87808
AI-assisted implementation and review.
What Problem This Solves
Fixes an issue where a Control UI user turn accepted immediately before a Gateway restart could disappear from the conversation without a final reply or a visible terminal outcome. The client received an acknowledgement, but the Gateway did not yet own enough durable state to resume that accepted work after startup.
Why This Change Was Made
Control UI admission now records the user transcript turn and its restart-recovery claim atomically before acknowledging the send. Startup recovery adopts that durable claim through a single-use, identity-bound runtime handoff, so the normal agent path executes the accepted turn exactly once without persisting the user message twice.
The canonical state remains in the existing per-agent
openclaw-agent.sqlitedatabase and itssession_entriesrows. Recovery uses indexed lifecycle status and claim fields; it does not add anunsentside table, JSON sidecar, dual-write path, or runtime file fallback. Terminal cancellation/failure state is retained so completed or intentionally stopped work is not replayed, while safe unadopted claims can be retried.Automatic replay is intentionally limited to plain Control UI user turns whose work can be reconstructed safely. Command/hook turns and other unsafe admission shapes retain explicit terminal behavior instead of being guessed or replayed.
User Impact
Users can restart the Gateway during a supported in-flight Control UI turn and expect the accepted turn to remain visible and resume once after reconnect. This removes a message-loss window without creating a second queue or a separate source of session truth.
The change also consolidates restart ownership into focused session, Gateway, and agent helpers, deletes duplicated lookup/persistence policy, and documents the recovery contract and boundaries.
Evidence
gateway-restart-inflight-runpassed with a real Gateway process restart and mock OpenAI provider (run_20624fe8f162).run_7089b0036e9d).run_a669fe9550cd).run_7341b9ec5383). SwiftLint remained owned by macOS CI because the Crabbox host was Linux.6df498c3cd1000b3f40f19cbd9cf63d6a1fb0c65.git range-diffreports all 12 commits patch-identical across the final rebases;git diff --checkpasses.