Skip to content

Bug: initSessionState uses raw session key while all read paths canonicalize — causes silent session loss on reconnect #29683

@sebbyjp

Description

@sebbyjp

Summary

initSessionState writes session entries to sessions.json using the raw key from resolveSessionKey() (e.g. "settings2"), but every read path (chat.history, sessions.list, sessions.patch) uses resolveSessionStoreKey() which canonicalizes the key (e.g. "agent:main:settings2").

After any WebSocket reconnect (gateway restart, config reload, network blip), the UI calls chat.history with the canonicalized key, finds no entry, and the next chat.send creates a fresh session UUID — silently orphaning the old transcript.

Root Cause

Write path (initSessionState in src/auto-reply/reply/session.ts):

sessionKey = resolveSessionKey(sessionScope, sessionCtxForState, mainKey);
// → returns raw key as-is (e.g. "settings2")

const entry = sessionStore[sessionKey]; // lookup under "settings2"
// ...
store[sessionKey] = sessionEntry; // saves under "settings2"

Read path (loadSessionEntry in src/gateway/session-utils.ts):

const canonicalKey = resolveSessionStoreKey({ cfg, sessionKey });
// → canonicalizes to "agent:main:settings2"

const entry = store[canonicalKey]; // lookup under "agent:main:settings2" — NOT FOUND

The two functions resolve the same input to different keys. The store write uses the short form; every subsequent read uses the canonical form. They never match.

Reproduction

  1. Open the Control UI webchat
  2. Type a custom session name (e.g. settings2) in the session key input
  3. Send a few messages (session is created and works)
  4. Trigger a gateway restart (config.patch, config.apply, SIGUSR1, or just wait for a reconnect)
  5. The webchat reconnects and calls chat.history — returns empty (key mismatch)
  6. Send a new message — a brand new session UUID is generated
  7. The old transcript file is now orphaned on disk

Fix

Canonicalize the key in initSessionState before any store read/write:

// Before:
sessionKey = resolveSessionKey(sessionScope, sessionCtxForState, mainKey);

// After:
const rawSessionKey = resolveSessionKey(sessionScope, sessionCtxForState, mainKey);
sessionKey = resolveSessionStoreKey({ cfg, sessionKey: rawSessionKey });

This ensures all paths (write during chat.send, read during chat.history, read during sessions.list) use the identical canonical key.

Consolidates These Issues

This is the shared root cause behind multiple open issues:

All of these describe the same symptom: after a restart or reconnect, the gateway cannot find the existing session entry and creates a new one. The orphaned files pile up.

Environment

  • OpenClaw: 2026.2.22-2
  • OS: macOS (arm64)
  • Channel: webchat (Control UI)
  • Restart method: in-process SIGUSR1 (config.patch)

Evidence

Traced from source at dist/auto-reply/reply/session.js and dist/gateway/session-utils.js. Confirmed via gateway logs showing session file creation timestamps matching the exact moment of the first post-reconnect chat.send, not the restart itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions