Skip to content

Claude CLI false session reset: directChatContext provider label drifts extraSystemPromptHash when one session key serves multiple channels (dmScope=main) #83250

Description

@gorkem2020

Summary

Sibling of #82812 (closed) and #69118 (open). #82812 fixed per-turn metadata bleeding into extraSystemPromptHash by hashing only the static parts. However one of those "static" parts, directChatContext, embeds the channel/provider label, which is NOT stable when a single CLI session binding serves more than one channel. With session.dmScope:"main" (or any route binding that collapses multiple delivery channels onto one session key), alternating the channel a direct conversation arrives on flips directChatContext between, for example, "Telegram" and "WebChat". That changes extraSystemPromptHash, so resolveCliSessionReuse returns invalidatedReason: "system-prompt" and the agent starts a fresh claude session with no memory of the prior turns.

Net user-visible effect: with claude-cli backend plus dmScope:"main", moving a conversation between the dashboard and Telegram (same agent, same logical DM) makes the agent lose all prior context on the switch turn. Confirmed reproducible on OpenClaw 2026.5.7.

Root cause (source, 2026.5.7)

  • src/auto-reply/reply/get-reply-run.ts builds the reuse-hash input:
    extraSystemPromptStaticParts = [directChatContext, groupChatContext, groupIntro, groupSystemPrompt, execOverrideHint] (comment: "Static parts only (no per-message inbound metadata) for CLI session reuse hashing"). This is the bug: per-turn dynamic context bleeds into extraSystemPromptHash, triggering false cli session reset + A-type empty output #82812 mitigation, and it correctly excludes inboundMetaPrompt.
  • src/auto-reply/reply/groups.ts buildDirectChatContext emits You are in a ${providerLabel} direct conversation. and resolveProviderLabel (same file) returns "WebChat" for internal channels and the capitalized provider name (for example "Telegram") otherwise. So the string is channel dependent.
  • src/agents/cli-runner/prepare.ts hashes that block (extraSystemPromptHash = hashCliSessionText(params.extraSystemPromptStatic ...)).
  • src/agents/cli-session.ts resolveCliSessionReuse: if (storedExtraSystemPromptHash !== currentExtraSystemPromptHash) return { invalidatedReason: "system-prompt" }.
  • Under session.dmScope:"main", direct conversations from different channels resolve to the same session key (one cliSessionBindings.claude-cli entry, one cached hash). The provider label is "static within a channel" but "variant across channels sharing one key", so the static-parts assumption (one session key implies one stable context) does not hold once channels are collapsed.

The same class as #82812 / #69118 / #64386, but the trigger is cross-channel session collapse rather than per-turn metadata or group intro drift, and it is NOT covered by the static-parts split that closed #82812.

Reproduction (deterministic, no restart, no compaction)

  1. Agent on claude-cli backend. Config session.dmScope: "main". Agent reachable on two channels (for example dashboard WebChat and Telegram DM).
  2. Send a direct message on channel A (Telegram). Note the reply; confirm log shows reuse=reusable.
  3. Within a couple of minutes (inside the idle window, no restart), send a direct message to the same agent on channel B (dashboard).
  4. Observe gateway log on the channel B turn:
    cli session reset: provider=claude-cli reason=system-prompt
    cli exec: provider=claude-cli ... useResume=false reuse=invalidated:system-prompt
  5. The channel B reply has no memory of the channel A turn. Switching back to channel A resets again.

Expected: a single logical DM that the operator intentionally consolidated via dmScope:"main" reuses one CLI session across channels; turn N+1 remembers turn N regardless of which channel delivered it.

Actual: every channel switch invalidates the CLI session and wipes conversational context.

Confirmed reproduction (operator capture, OpenClaw 2026.5.7, logs scrubbed)

Before the test: 2+ hours of continuous single-channel (Telegram DM) turns, every one reusing one CLI session:

21:55:52  cli exec: provider=claude-cli model=opus ... useResume=true session=present resumeSession=<sid> reuse=reusable historyPrompt=none
22:31:45  cli exec: provider=claude-cli model=opus ... useResume=true session=present resumeSession=<sid> reuse=reusable historyPrompt=none
22:31:52  [telegram] sendMessage ok            (channel A, still reusing the same session)

Channel switch (Telegram to dashboard WebChat):

22:33:01  cli session reset: provider=claude-cli reason=system-prompt
22:33:01  cli exec: provider=claude-cli model=opus ... useResume=false session=none resumeSession=none reuse=invalidated:system-prompt historyPrompt=none
22:34:04  [ws] webchat disconnected ...

Switch back (WebChat to Telegram):

22:34:30  cli session reset: provider=claude-cli reason=system-prompt
22:34:30  cli exec: provider=claude-cli model=opus ... useResume=false session=none resumeSession=none reuse=invalidated:system-prompt historyPrompt=none
22:34:35  [telegram] sendMessage ok

Two involuntary resets within 90 seconds, each reuse=invalidated:system-prompt with historyPrompt=none (full context wipe), triggered purely by alternating the delivery channel of one logical DM, after 2+ hours of stable single-channel reuse of the same CLI session. No restart, no config change, no compaction in the window.

Impact

Operators who set dmScope:"main" specifically to get one continuous assistant across channels get the opposite: guaranteed context loss on every channel switch. Severity is amplified by the absence of a raw-message reseed fallback for the claude-cli backend (see #58818): each of these resets is a total wipe, not a graceful re-seed.

Suggested direction (non-prescriptive)

Any one of: (a) exclude the channel/provider label from directChatContext when it feeds extraSystemPromptStaticParts (normalize the provider token out of the reuse-hash input, the same spirit as the #82812 metadata exclusion); (b) keep the channel context in the prompt but not in the reuse identity; (c) scope the cliSessionBindings entry per delivery channel even when the logical session key is collapsed by dmScope:"main".

Environment

OpenClaw 2026.5.7, claude-cli backend, session.dmScope: "main", agent reachable on dashboard WebChat and Telegram DM. Self-hosted Docker gateway.

Related: #82812 (closed, per-turn metadata variant), #69118 (open, group intro variant), #64386 (mcpConfigHash sibling), #58818 (missing raw-tail fallback that makes this catastrophic).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.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