You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In OpenClaw v2026.5.2, the webchat session JSONL transcript is overwritten on every turn — only the latest message exchange survives. On page refresh, all previous messages are gone. This worked correctly on v2026.4.29.
Root cause
mirrorCodexAppServerTranscript (in run-attempt-*.js) receives the full conversation history via messagesSnapshot (built from readMirroredSessionHistoryMessages at line 3264) and rewrites ALL messages to the JSONL every turn. Each message is written via appendCodexAppServerTranscriptMessage, which calls migrateLinearTranscriptToParentLinked — a function that reads the entire file and rewrites it via fs.writeFile (not append).
The per-turn idempotencyScope (codex-app-server:${threadId}:${turnId}) changes every turn, so old messages get new keys and bypass the idempotency check. The rewrite creates new entries with different parentId chains, causing selectBoundedActiveTailRecords (the tree walk) to follow the newest branch and orphan previous entries.
In v4.29, SessionManager.open().appendMessage() truly appended to the file. The SessionManager was removed in 5.2 and replaced with this read-migrate-rewrite cycle.
Steps to reproduce
Open webchat on v2026.5.2
Send message 1, receive response
Send message 2, receive response
Check the session JSONL — message 1's assistant response is gone
Refresh page — only message 2 and its response are visible
Evidence
Controlled test on macOS arm64, v2026.5.2 (8b2a6e5), stock dist (no patches except prewarm skip):
Took a JSONL snapshot between turns
Diffed after the next turn
The previous assistant response (id 44b4ec62, parentId d3471f25) was replaced by the new user message (id 9f4b19a1, parentId d3471f25) — same parent, sibling branch
The tree walk follows the new branch, orphaning the old assistant entry
9 minutes between turns — no race condition, purely the mirror rewrite logic
Contributing factors
migrateLinearTranscriptToParentLinked rewrites via writeFile — any concurrent access or stale read loses data
Per-turn turnId in idempotency scope — old messages get new keys, bypassing dedup
appendCodexAppServerTranscriptMessage calls migration inside the lock but mirrorCodexAppServerTranscript takes a separate lock per batch — interleaving between the webchat write path and the mirror write path can race
Bug type
Regression (worked before, now fails)
Beta release blocker
No
Summary
In OpenClaw v2026.5.2, the webchat session JSONL transcript is overwritten on every turn — only the latest message exchange survives. On page refresh, all previous messages are gone. This worked correctly on v2026.4.29.
Root cause
mirrorCodexAppServerTranscript(inrun-attempt-*.js) receives the full conversation history viamessagesSnapshot(built fromreadMirroredSessionHistoryMessagesat line 3264) and rewrites ALL messages to the JSONL every turn. Each message is written viaappendCodexAppServerTranscriptMessage, which callsmigrateLinearTranscriptToParentLinked— a function that reads the entire file and rewrites it viafs.writeFile(not append).The per-turn
idempotencyScope(codex-app-server:${threadId}:${turnId}) changes every turn, so old messages get new keys and bypass the idempotency check. The rewrite creates new entries with differentparentIdchains, causingselectBoundedActiveTailRecords(the tree walk) to follow the newest branch and orphan previous entries.In v4.29,
SessionManager.open().appendMessage()truly appended to the file. TheSessionManagerwas removed in 5.2 and replaced with this read-migrate-rewrite cycle.Steps to reproduce
Evidence
Controlled test on macOS arm64, v2026.5.2 (8b2a6e5), stock dist (no patches except prewarm skip):
44b4ec62, parentIdd3471f25) was replaced by the new user message (id9f4b19a1, parentIdd3471f25) — same parent, sibling branchContributing factors
migrateLinearTranscriptToParentLinkedrewrites viawriteFile— any concurrent access or stale read loses dataturnIdin idempotency scope — old messages get new keys, bypassing dedupreleaseHeldLockdeletesHELD_LOCKSbefore async cleanup, allowing concurrent acquirers on macOSappendCodexAppServerTranscriptMessagecalls migration inside the lock butmirrorCodexAppServerTranscripttakes a separate lock per batch — interleaving between the webchat write path and the mirror write path can raceCode references (v2026.5.2 dist)
run-attempt-CektiLYp.js:3264—finalMessages = readMirroredSessionHistoryMessages(sessionFile)(full history)run-attempt-CektiLYp.js:3598—idempotencyScope: codex-app-server:${threadId}:${turnId}(per-turn keys)run-attempt-CektiLYp.js:2082—appendCodexAppServerTranscriptMessagetriggers migrationtranscript-C_uDP9Gl.js:121—migrateLinearTranscriptToParentLinkedrewrites file viawriteFiletranscript-C_uDP9Gl.js:156—fs.writeFile(transcriptPath, ...)— destructive overwritesession-utils.fs-W0CAUUsv.js:564—selectBoundedActiveTailRecordstree walk follows one branchWhat changed (4.29 → 5.2)
SessionManager.open().appendMessage()— true appendappendCodexAppServerTranscriptMessage→migrateLinearTranscriptToParentLinked→writeFile— full rewritereadSessionMessages— sync, reads allreadRecentSessionMessagesAsync— async, tail window + tree walkSessionManager.open().getBranch()selectBoundedActiveTailRecords— drops orphaned branchesmessagesSnapshot(entire history)Environment
Related issues