Skip to content

Session file rotation breaks bootstrap; compaction never triggers #192

@glfruit

Description

@glfruit

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:

  1. bootstrapState.sessionFilePath === params.sessionFile becomes false, so the unchanged-file fast path is skipped.
  2. The append-only incremental path is also skipped for the same reason.
  3. Control falls through to reconcileSessionTail().
  4. After a reset, the new file has no overlap with the old DB rows, so reconciliation returns hasOverlap = false and importedMessages = 0.
  5. The current code only persists the new bootstrap state when hasOverlap === true.
  6. 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 constlet
  • bootstrapState from constlet

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions