Bug: Session file rotation breaks bootstrap — new session messages never ingest, compaction never triggers
Environment
- lossless-claw: 0.5.1
- OpenClaw: gateway daemon mode
- Platform: macOS (darwin arm64), Node.js v25.x
Description
When OpenClaw rotates a session file (for example after /reset, creating a new UUID .jsonl leaf), LCM's bootstrap() logic keeps tracking the old sessionFilePath in conversation_bootstrap_state.
As a result, messages from the new session file never get imported into the LCM database, token counts stop growing, compaction never triggers, and the main session context can grow until it overflows.
Root cause
In src/engine.ts, bootstrap state is keyed by conversation and stores the absolute sessionFilePath.
Current behavior on rotation:
bootstrapState.sessionFilePath === params.sessionFile becomes false, so the unchanged-file fast path is skipped.
- The append-only incremental path is also skipped for the same reason.
- Control falls through to
reconcileSessionTail().
- After a reset, the new file has no overlap with the old DB rows, so reconciliation returns
hasOverlap = false and importedMessages = 0.
- The current code only persists the new bootstrap state when
hasOverlap === true.
- If
conversation.bootstrappedAt is already true, bootstrap returns "already bootstrapped".
This creates a dead loop:
- stale
sessionFilePath remains in DB
- every future bootstrap retries against the rotated file
- no new messages are imported
- compaction never triggers because DB token counts never increase
User-visible impact
- new session messages missing from LCM DB
compaction_events can stay at 0 indefinitely
- contexts continue growing without compaction
- eventually sessions hit context overflow
Proposed fix
Detect session file rotation early in bootstrap(), before the existing path-comparison guards.
If bootstrapState.sessionFilePath !== params.sessionFile, treat the conversation as a fresh post-reset transcript:
- purge conversation-scoped rows tied to the old transcript
- clear
conversation_bootstrap_state
- reset local bootstrap variables (
bootstrapState = null, existingCount = 0)
- re-enter the first-import path for the new file
That means updating:
existingCount from const → let
bootstrapState from const → let
and adding a rotation-reset branch before the current sessionFilePath === params.sessionFile checks.
Trade-off
This discards old summaries for the rotated session lineage, but that seems acceptable because /reset semantically starts a new conversation and old summaries no longer describe the active transcript.
Notes
I locally hotfixed my installed copy with exactly this early rotation-reset branch, and the currently published 0.5.1 source still appears to lack that guard.
Bug: Session file rotation breaks bootstrap — new session messages never ingest, compaction never triggers
Environment
Description
When OpenClaw rotates a session file (for example after
/reset, creating a new UUID.jsonlleaf), LCM'sbootstrap()logic keeps tracking the oldsessionFilePathinconversation_bootstrap_state.As a result, messages from the new session file never get imported into the LCM database, token counts stop growing, compaction never triggers, and the main session context can grow until it overflows.
Root cause
In
src/engine.ts, bootstrap state is keyed by conversation and stores the absolutesessionFilePath.Current behavior on rotation:
bootstrapState.sessionFilePath === params.sessionFilebecomes false, so the unchanged-file fast path is skipped.reconcileSessionTail().hasOverlap = falseandimportedMessages = 0.hasOverlap === true.conversation.bootstrappedAtis already true, bootstrap returns"already bootstrapped".This creates a dead loop:
sessionFilePathremains in DBUser-visible impact
compaction_eventscan stay at 0 indefinitelyProposed fix
Detect session file rotation early in
bootstrap(), before the existing path-comparison guards.If
bootstrapState.sessionFilePath !== params.sessionFile, treat the conversation as a fresh post-reset transcript:conversation_bootstrap_statebootstrapState = null,existingCount = 0)That means updating:
existingCountfromconst→letbootstrapStatefromconst→letand adding a rotation-reset branch before the current
sessionFilePath === params.sessionFilechecks.Trade-off
This discards old summaries for the rotated session lineage, but that seems acceptable because
/resetsemantically starts a new conversation and old summaries no longer describe the active transcript.Notes
I locally hotfixed my installed copy with exactly this early rotation-reset branch, and the currently published 0.5.1 source still appears to lack that guard.