Skip to content

[Bug]: WebChat session silently resets on first message after daily 4 AM boundary — stale canReuseSession renames transcript to .jsonl.reset.* with no warning #88991

Description

@KrasimirKralev

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

When a WebChat client reconnects after a sleep cycle, gateway restart, or network disconnect that spans the daily session-reset boundary (default: 04:00 local), the first chat.send after reconnect silently starts a new session. The old session transcript is renamed to <id>.jsonl.reset.<ISO-timestamp>, the chat history is wiped from the UI, and no error or warning is shown. Users report this as "messages disappeared" or "the conversation was lost."

This is a code-level analysis of the root cause, filed to complement the symptom reports in #87700 and #70330 (gateway-restart variant), neither of which identifies the specific check that causes the silent reset.

Steps to reproduce

  1. Open the Control UI WebChat. Exchange several messages. Note the active session ID.
  2. Leave the browser open and let the host machine sleep through 04:00 (the default session.reset.atHour).
  3. Wake the machine. The WebChat still shows the prior conversation (UI has not refreshed).
  4. Type any new message and send it.

Result: The chat clears instantly, no error is shown, the old .jsonl transcript is renamed to <id>.jsonl.reset.*, and the conversation is effectively lost from the UI. The Docker/gateway log shows no explicit /reset command — the reset is a side-effect of the freshness check.

Expected behavior

Either:

  • (A) The session is resumed silently without rename (if the 4 AM boundary has not crossed OR a grace period is respected), OR
  • (B) The UI receives a proactive stale-session event so it can display a clear "Starting a new session — previous session archived" notice before the user types a message.

The current behavior (silently discard history on first send) gives no opportunity for the user to save or copy the prior conversation.

Actual behavior

The session is silently replaced. <id>.jsonl is archived to <id>.jsonl.reset.<ISO>. The Control UI clears and shows an empty conversation. The user only discovers the loss after sending their next message.

Code analysis (root cause — analysis, not observed in live environment)

The gateway evaluates session freshness in src/gateway/server-methods/agent.ts (line 1657–1660) before deciding whether to reuse an existing session:

// src/gateway/server-methods/agent.ts:1657-1660
const canReuseSession =
  Boolean(entry?.sessionId) &&
  (freshness?.fresh ?? false) &&      // ← fails when staleDaily=true
  !failedSessionTranscriptMissing;

freshness is produced by evaluateSessionFreshness in src/config/sessions/reset-policy.ts:

// src/config/sessions/reset-policy.ts:84-93
const dailyResetAt =
  params.policy.mode === "daily"
    ? resolveDailyResetAtMs(params.now, params.policy.atHour)  // default: 4 AM today
    : undefined;
const staleDaily = dailyResetAt != null && sessionStartedAt < dailyResetAt;
// ...
return { fresh: !(staleDaily || staleIdle), ... };

resolveDailyResetAtMs returns the most recent 04:00 wall-clock timestamp. Any session whose sessionStartedAt predates that timestamp is considered stale (fresh = false).

Default values (src/config/sessions/reset-policy.ts:21-22):

export const DEFAULT_RESET_MODE: SessionResetMode = "daily";
export const DEFAULT_RESET_AT_HOUR = 4;

When canReuseSession = false and no requestedSessionId is provided, a new UUID is generated and isNewSession = true (agent.ts:1668-1671). This triggers archiveSessionTranscriptsForSessionDetailed with reason: "reset", which renames the transcript (session-reset-service.ts:964-969).

The WebChat client has no signal that the session is stale until after the first chat.send completes. At that point the UI observes a session rotation event and clears the chat display — indistinguishable from a silent data loss.

Hypothesis

Fix options (A/B/C — not implemented, proposed for discussion):

Option A — Grace period on reconnect: After a WebSocket reconnect event, delay freshness evaluation by N minutes (e.g. 5 min). This prevents the 4 AM boundary from triggering mid-session for users who simply had a brief disconnect. Lowest UI impact but reduces daily-reset reliability.

Option B — Proactive stale notification: When a webchat client re-connects (identified via isWebchatConnect), check if its current session is stale and emit a session.stale event to the client. The UI can then display "Your session was reset at 4:00 AM — previous conversation is archived" before the user sends a message. Requires a new event type on the client.

Option C — Explicit resume intent in chat.send: Require webchat clients to include the known sessionId in every chat.send. If the gateway detects a stale session but the client is resuming the same sessionId, respond with a structured session.reset_pending error carrying the new sessionId, giving the UI a chance to surface a notice and refresh. Requires a protocol change.

Option B has the best user-experience profile and is consistent with how other notifications (session compaction, memory, heartbeat) are pushed to the Control UI.

Related issues

OpenClaw version

Analyzed against main @ 01dda73e9bb1ecb787e2762612fc3bb586d731a0 (confirmed by #87700 reporter). Behavior reproduced on 2026.5.5+.

Operating system

Confirmed on macOS + Docker (arm64) per #87700; same code path applies to all platforms.

Install method

Not applicable — this is a code-path analysis.

Additional information

Relevant files (for reviewer orientation):

  • src/config/sessions/reset-policy.tsevaluateSessionFreshness, resolveDailyResetAtMs, DEFAULT_RESET_MODE, DEFAULT_RESET_AT_HOUR
  • src/gateway/server-methods/agent.tscanReuseSession check (line 1657–1672), isNewSession assignment (line 1668–1672)
  • src/gateway/session-reset-service.tsarchiveSessionTranscriptsForSessionDetailed (line 964) — produces the .jsonl.reset.* files

AI-assisted disclosure: This issue was filed by an automated contribution scout. The code analysis is static (grep + file reads on the source tree), not live observation. The hypothesis sections are marked as such. A human should verify the suggested fix shape before implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions