fix(sessions): preserve corrupt-header transcripts#89050
Conversation
|
Codex review: needs changes before merge. Reviewed June 2, 2026, 6:57 AM ET / 10:57 UTC. Summary PR surface: Source +103, Tests +224. Total +327 across 4 files. Reproducibility: yes. The linked issue and PR comments provide a high-confidence source reproduction through 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:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Keep the recovery-in- Do we have a high-confidence way to reproduce the issue? Yes. The linked issue and PR comments provide a high-confidence source reproduction through Is this the best way to solve the issue? No, not yet. Moving recovery into Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 1db2c2a3e058. Label changesLabel justifications:
Evidence reviewedPR surface: Source +103, Tests +224. Total +327 across 4 files. View PR surface stats
Security concerns:
Acceptance criteria:
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
|
|
Thanks for the review. I cannot honestly provide live production resume proof against a real corrupted user transcript from this contributor environment without touching private/local session data. What I can confirm publicly is the focused behavior proof in this PR: the regression uses the real filesystem-backed Verification already run:
The remaining live proof item needs a maintainer or safe disposable session fixture that can reproduce the production resume path without exposing a real user's transcript. |
|
Thanks for jumping on this @charles-openclaw — but I think this fix guards the wrong layer, and #89037's data loss is still reproducible on this branch ( The real truncation happens before
|
|
Thanks, that reproduction is exactly right. I pushed follow-up commit Summary:
Verification:
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Thanks, the backup-mode finding was right. I pushed follow-up commit Summary:
Verification:
I still cannot honestly provide a real private production resume recording from this contributor environment without using local/private session state. The disposable filesystem-backed regression now covers the recovered transcript contents plus backup permissions, and the remaining live proof item is still best done by a maintainer or a safe disposable OpenClaw session setup. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Re-checked on
So #89037's data loss is resolved. One small residual worth tightening before merge: Header-only corrupt file duplicate headerWhen the corrupt file has no recoverable messages (e.g. a crash left only a half-written header line), writeFileSync(file, `{"type":"session","version":3,"id":"sess`); // only a truncated header, no messages
const sm = SessionManager.open(file);
sm.appendMessage(msg("user", "hello"));
sm.appendMessage(msg("assistant", "hi"));
// final types: ['session','session','message','message'] -> headerCount: 2 (expected 1)No data is lost here (there were no messages), so it's low severity — just a malformed two-header transcript. It's the same // after building recoveredEntries
const hasAssistant = recoveredEntries.some(
(e) => e.type === "message" && e.message.role === "assistant",
);
this.fileEntries = recoveredEntries;
const header = this.fileEntries.find((e) => e.type === "session");
this.sessionId = header?.id?? createSessionId();
this.buildIndex();
if (hasAssistant) {
this.rewriteFile();
this.flushed = true;
} else {
this.flushed = false; // defer to persist(); avoids the duplicate-header re-flush
}
return;(That also keeps the recovery branch consistent with the deferred-write contract documented in |
|
I added a disposable real-path proof run for the current PR head after the latest review. This did not touch any real user/session state; it used a temp transcript under Summary:
Command shape used locally: node --import tsx <<'NODE'
# creates a temp corrupt JSONL transcript, chmods it 0600,
# calls SessionManager.open(...), checks recovered entries and backup mode,
# prints only synthetic/redacted proof data, then removes the temp dir
NODEObserved proof output: {
"tempRoot": "/tmp/<redacted-temp-dir>",
"openCall": "SessionManager.open(disposableCorruptSession, tempDir, /tmp/disposable-openclaw-proof)",
"beforeMode": "0600",
"afterMode": "0600",
"backupMode": "0600",
"recoveredEntryIds": ["proof-user-1", "proof-assistant-1"],
"activeTranscriptHasUserMarker": true,
"activeTranscriptHasAssistantMarker": true,
"backupByteLength": 390,
"backupMatchesOriginal": true,
"backupFileNamePattern": "disposable-corrupt-session.jsonl.corrupt-<timestamp>.jsonl"
}The remaining policy question is intentional: this PR preserves the original corrupt bytes in a same-directory, same-mode @clawsweeper re-review |
|
🦞👀 Command router queued. I will update this comment with the next step. Re-review progress:
|
|
@clawsweeper re-review Thanks, the header-only recovery edge case was a good catch. I pushed follow-up commit Summary:
Verification:
|
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Maintainer fix pushed: What changed:
Local proof on this branch after the fix:
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Land-ready maintainer proof for head Work done:
Local proof run:
Real behavior proof:
Known proof gap:
|
Summary
SessionManager.open()before the destructive rewrite path can run..corrupt-*backup, then rewrite the active transcript as valid JSONL.Linked context
Closes #89037.
Real behavior proof (required for external PRs)
SessionManager.open()->prepareSessionManagerForRun()-> first assistant append path.SessionManager.open()andprepareSessionManagerForRun()against disposable JSONL session files under the OS temp directory.node --import tsx .artifacts/corrupt-session-proof.ts..corrupt-*backup is created, and the recovered user-only production path keeps the user row before appending the first assistant row.mainrewrites the corrupted-header transcript to a fresh one-line session header whenSessionManager.open()receives a non-empty file whose first parseable entry is not a session header.Tests and validation
pnpm test src/agents/sessions/session-manager.test.ts src/agents/embedded-agent-runner/session-manager-init.test.ts -- --reporter=dotpassed 2 files / 6 tests.pnpm tsgo:prodpassed.pnpm check:test-typespassed.pnpm lint --threads=8passed.git diff --check -- src/agents/sessions/session-manager.ts src/agents/sessions/session-manager.test.ts src/agents/embedded-agent-runner/session-manager-init.ts src/agents/embedded-agent-runner/session-manager-init.test.tspassed..agents/skills/autoreview/scripts/autoreview --mode localreported no accepted/actionable findings.Risk checklist
Current review state
Maintainer fix
30307017a8pushed and ClawSweeper re-review requested.