openclaw - 💡(How to fix) Fix Claude CLI false session reset: directChatContext provider label drifts extraSystemPromptHash when one session key serves multiple channels (dmScope=main)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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

Root cause (source, 2026.5.7)

Fix Action

Fix / Workaround

  • 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 #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.

Code Example

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)

---

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 ...

---

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
RAW_BUFFERClick to expand / collapse

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 #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).

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING