fix(sessions): exclude done sessions from transcript freshness rollover guard#99985
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 1:54 AM ET / 05:54 UTC. Summary PR surface: Source +5, Tests +93. Total +98 across 4 files. Reproducibility: yes. At source level, current main lets a Review metrics: 1 noteworthy metric.
Stored data model 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. Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Land one canonical lifecycle-helper fix that skips the guard for successful Do we have a high-confidence way to reproduce the issue? Yes. At source level, current main lets a Is this the best way to solve the issue? Yes. The shared lifecycle helper is the narrowest maintainable boundary, and the PR updates all three caller expectations while preserving endedAt-only and interrupted-state rollover; the remaining question is canonical branch selection. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 62d957634c53. Label changesLabel justifications:
Evidence reviewedPR surface: Source +5, Tests +93. Total +98 across 4 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
Review history (5 earlier review cycles)
|
|
@clawsweeper re-review All ClawSweeper findings have been addressed: |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
|
Reviewed at head Confirmed the bug on Good points:
One thing that needs a maintainer decision before merge: this overlaps with #99975 (and #99971, named in the body). #99975 makes a functionally identical change to the same lines in Minor (non-blocking): the combined branch now shares one comment block for two different rationales (failed = retry/recovery, done = persistence-ordering). If you keep this PR, consider a one-line separation so the I did not run the suite locally; the PR's cited |
edd1b14 to
ee92570
Compare
…ollover guard Exclude status: "done" from resolveTerminalMainSessionTranscriptRegistryCheck alongside the already-excluded status: "failed". Successful main sessions should stay reusable for the next user message even when transcript mtime slightly exceeds registry updatedAt. Updated test expectations in: - src/gateway/server-methods/agent.test.ts (split it.each into done-reuse + endedAt-only-rotate) - src/commands/agent.session.test.ts (split into done-reuse + endedAt-only-rotate) - src/auto-reply/reply/session.test.ts (default-done case now expects reuse) Related to openclaw#99964 Co-Authored-By: Claude Opus 4.8 <[email protected]>
Remove the conditional status spread that TS cannot resolve when status is absent from the scenario type, and add 'as const' for narrowed literal inference.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
ee92570 to
cd1b627
Compare
|
Maintainer repair is land-ready at
Known gap: no released binary was seeded; deterministic filesystem/session-store regressions cover every admission caller and terminal-state branch. |
|
Merged via squash.
|
…er guard (openclaw#99985) * [AI] fix(sessions): exclude done sessions from transcript freshness rollover guard Exclude status: "done" from resolveTerminalMainSessionTranscriptRegistryCheck alongside the already-excluded status: "failed". Successful main sessions should stay reusable for the next user message even when transcript mtime slightly exceeds registry updatedAt. Updated test expectations in: - src/gateway/server-methods/agent.test.ts (split it.each into done-reuse + endedAt-only-rotate) - src/commands/agent.session.test.ts (split into done-reuse + endedAt-only-rotate) - src/auto-reply/reply/session.test.ts (default-done case now expects reuse) Related to openclaw#99964 Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(ts): fix TypeScript error in endedAt-only test scenario type Remove the conditional status spread that TS cannot resolve when status is absent from the scenario type, and add 'as const' for narrowed literal inference. * [AI] fix(comment): correct mtime direction in done exclusion comment Co-Authored-By: Claude Opus 4.8 <[email protected]> * review: separate failed/done rationale in comment * fix(sessions): reuse completed main transcripts --------- Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
…er guard (openclaw#99985) * [AI] fix(sessions): exclude done sessions from transcript freshness rollover guard Exclude status: "done" from resolveTerminalMainSessionTranscriptRegistryCheck alongside the already-excluded status: "failed". Successful main sessions should stay reusable for the next user message even when transcript mtime slightly exceeds registry updatedAt. Updated test expectations in: - src/gateway/server-methods/agent.test.ts (split it.each into done-reuse + endedAt-only-rotate) - src/commands/agent.session.test.ts (split into done-reuse + endedAt-only-rotate) - src/auto-reply/reply/session.test.ts (default-done case now expects reuse) Related to openclaw#99964 Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(ts): fix TypeScript error in endedAt-only test scenario type Remove the conditional status spread that TS cannot resolve when status is absent from the scenario type, and add 'as const' for narrowed literal inference. * [AI] fix(comment): correct mtime direction in done exclusion comment Co-Authored-By: Claude Opus 4.8 <[email protected]> * review: separate failed/done rationale in comment * fix(sessions): reuse completed main transcripts --------- Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
Successful main sessions (
status: "done") can roll over to new session IDs and archive transcript as.jsonl.reset.*when the transcript file mtime is slightly newer than the registryupdatedAt, even though the session completed successfully.resolveTerminalMainSessionTranscriptRegistryCheckapplies the transcript freshness guard tostatus: "done"rows, forcing unnecessary session rotation on the next inbound message.status: "done"from the guard alongside the already-excludedstatus: "failed", so successful sessions stay reusable.src/config/sessions/lifecycle.ts(core fix), test expectations in 3 test files.killed/timeoutstill guarded;endedAt-onlyterminal rows still guarded; no config/API/docs changes.Change Type
Scope
Linked Issue/PR
Motivation
After a main session completes successfully (
status: "done"), the next user message should reuse the same session. However, when the transcript file's mtime is slightly newer than the session registry'supdatedAt(which can happen due to filesystem I/O timing — the transcript write may land after the registry update),hasTerminalMainSessionTranscriptNewerThanRegistryreturnstrue, causingcanReuseSessionto befalse. This forces a new UUID session and archives the old transcript as.jsonl.reset.*.Three call paths are affected:
src/gateway/server-methods/agent.ts:1956-1970)src/agents/command/session.ts:379-389)src/auto-reply/reply/session.ts:548)All three use the same shared guard function, so fixing it in one place covers all paths.
Evidence
Behavior addressed:
resolveTerminalMainSessionTranscriptRegistryCheck()now returnsundefinedforstatus: "done"rows, preventing transcript-mtime-based rollover for successful completions.Real environment tested: Linux x86_64, Node 22.19, branch
fix/done-session-transcript-guard-99964Exact steps or command run after this patch:
src/commands/agent.session.test.tssrc/auto-reply/reply/session.test.ts(terminal)src/gateway/server-methods/agent.test.ts(terminal)Observed result after fix:
status: "done"sessions with newer transcript mtime no longer trigger session rolloverfailedsessions remain reusable (existing behavior preserved)killed/timeout/endedAt-onlysessions remain guarded (existing behavior preserved)What was not tested: Full Gateway with a real remote model provider — the session reuse decision happens before any model call, so mocked model responses in the Gateway tests prove the same code path. All 4 layers (guard function, CLI resolveSession, Gateway handler, auto-reply) are covered.
Root Cause
In
resolveTerminalMainSessionTranscriptRegistryCheck()(src/config/sessions/lifecycle.ts:162-184):isTerminalSessionStatus()includes"done","failed","killed","timeout"— but only"failed"was excluded from the mtime check, leaving"done"(successful completion) unprotected.Provenance: Introduced in
af906225fa(Fermin Quant, 2026-06-25) as part of PRs #0c9ac48d and #57bed6ae which added the terminal-main transcript freshness guard.Alternative Approach Considered
Two approaches were considered:
Option A (chosen): Exclude
status: "done"from the transcript freshness guard. Minimal change, low risk.Option B (not chosen): Fix the persistence ordering so transcript mtime never exceeds registry
updatedAt. This addresses the root cause (filesystem I/O timing), but involves more code paths and complexity. The persistence ordering issue is a known technical debt that could be addressed as a follow-up for other terminal states.Option A was chosen because:
failure(same exclusion approach)killed,timeout,endedAt-only) where transcript-continuity recovery logic is still importantUser-visible / Behavior Changes
Successful main-agent sessions will no longer unexpectedly roll over to a new session ID when the transcript mtime is slightly newer than the registry
updatedAt. The.jsonl.reset.*archive pattern will no longer appear for normal successful runs.Security Impact
Regression Test Plan
src/gateway/server-methods/agent.test.tsforstatus: "done"case (now expects reuse instead of rotation)src/commands/agent.session.test.tsforstatus: "done"cases (now expects reuse)src/auto-reply/reply/session.test.tsfor the default-done caseendedAt-onlytest cases unchanged (still assert rotation — guard remains active)Human Verification
Compatibility / Migration
Best-fix Verdict
failedexclusion pattern.updatedAt >= transcript mtime. This is a deeper fix but involves more risk and wasn't necessary for this bug.Risks and Mitigations
status: "done"). Abnormal terminal states (killed,timeout,endedAt-only) remain fully guarded.failedexclusion.AI Assistance
Fixes #99964