You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
buildCliSessionHistoryPrompt in src/agents/cli-runner/session-history.ts (bundled as dist/session-history-*.js line ~41 in the published package) prefix-slices the rendered history when it exceeds maxHistoryChars, which drops the most recent assistant turns from the reseed prompt instead of the oldest. After PR #80934 made the Claude-CLI reseed path fire by default in v2026.5.12, every Claude-CLI user is exposed to this whenever session_expired triggers a reseed and the rendered transcript is longer than 12288 characters (the hard-coded default).
The defect
The current implementation:
constrenderedHistory=renderedHistoryRaw.length>maxHistoryChars
? `${renderedHistoryRaw.slice(0,maxHistoryChars).trimEnd()}\n[OpenClaw reseed history truncated]`
: renderedHistoryRaw;
params.messages is in chronological order (oldest → newest), and the flatMap(...).join("\n\n") preserves that order. slice(0, maxHistoryChars) is therefore a prefix slice that keeps the oldest content and drops the most recent — exactly the opposite of what a context-recovery prompt wants. The trailing [OpenClaw reseed history truncated] marker reinforces the wrong mental model: in current behavior, what actually got truncated is at the end, not the beginning.
Reproducer
In a Claude-CLI session whose rendered transcript exceeds ~12k chars (any moderate conversation — keyboard talk, code review, etc.), trigger a session_expired reseed (gateway restart, --safe drain, or any of the orphan-tool-use recovery paths).
The fresh CLI session receives a <conversation_history> block ending with the marker [OpenClaw reseed history truncated] planted mid-word — confirming a blind character slice with no message-boundary respect. The most recent assistant turns are absent. The model correctly diagnoses the truncation and replies with something like "I don't have context for what '...' is — the conversation history got truncated right at the end."
I have a concrete reproducer (Telegram + claude-cli backend, OC v2026.5.12) where the missing turn was visible in OC's session storage at ~/.openclaw/agents/<agent>/sessions/*.jsonl but absent from the reseed preamble at ~/.claude/projects/.../<sessionId>.jsonl records 0-7 of the post-recovery session. Happy to share the redacted JSONL excerpts if useful.
Proposed fix
One-character semantic change at the slice direction, plus moving the truncation marker to the lead so it correctly describes what follows:
This keeps the suffix (newest content) and drops the prefix (oldest content), which matches the intent of every other context-recovery feature I'm aware of in the codebase.
PR with the fix + a regression test will follow.
Why no test caught this
The existing tests in session-history.test.ts and prepare.test.ts (referenced in #79713 and #80905 review threads) assert that the prompt is built when expected and that the boundaries between sections are correct, but none assert that the last message in params.messages appears in the rendered prompt when the input exceeds the cap. A regression test of the form "given N messages totaling > maxHistoryChars, the last message MUST appear in the rendered prompt" would have caught this and would prevent regressions on the slice direction.
Scope
Universal: affects every claude-cli user on v2026.5.12+ when a reseed fires and the rendered history exceeds maxHistoryChars.
Backend-agnostic: the function is in shared cli-runner code, not in the claude-cli extension. Codex CLI / Gemini CLI hit the same path if they ever opt into the same reseed flag. (Only Claude CLI does today, per fix(anthropic): enable Claude CLI session-expired history reseed #80934.)
maxHistoryChars cap (12288 chars / ~3000 tokens) is unrelated — that's an independent config / sizing question. The orientation fix should land regardless of any future cap-tuning.
Summary
buildCliSessionHistoryPromptinsrc/agents/cli-runner/session-history.ts(bundled asdist/session-history-*.jsline ~41 in the published package) prefix-slices the rendered history when it exceedsmaxHistoryChars, which drops the most recent assistant turns from the reseed prompt instead of the oldest. After PR #80934 made the Claude-CLI reseed path fire by default in v2026.5.12, every Claude-CLI user is exposed to this wheneversession_expiredtriggers a reseed and the rendered transcript is longer than 12288 characters (the hard-coded default).The defect
The current implementation:
params.messagesis in chronological order (oldest → newest), and theflatMap(...).join("\n\n")preserves that order.slice(0, maxHistoryChars)is therefore a prefix slice that keeps the oldest content and drops the most recent — exactly the opposite of what a context-recovery prompt wants. The trailing[OpenClaw reseed history truncated]marker reinforces the wrong mental model: in current behavior, what actually got truncated is at the end, not the beginning.Reproducer
In a Claude-CLI session whose rendered transcript exceeds ~12k chars (any moderate conversation — keyboard talk, code review, etc.), trigger a
session_expiredreseed (gateway restart,--safedrain, or any of the orphan-tool-use recovery paths).The fresh CLI session receives a
<conversation_history>block ending with the marker[OpenClaw reseed history truncated]planted mid-word — confirming a blind character slice with no message-boundary respect. The most recent assistant turns are absent. The model correctly diagnoses the truncation and replies with something like "I don't have context for what '...' is — the conversation history got truncated right at the end."I have a concrete reproducer (Telegram + claude-cli backend, OC v2026.5.12) where the missing turn was visible in OC's session storage at
~/.openclaw/agents/<agent>/sessions/*.jsonlbut absent from the reseed preamble at~/.claude/projects/.../<sessionId>.jsonlrecords 0-7 of the post-recovery session. Happy to share the redacted JSONL excerpts if useful.Proposed fix
One-character semantic change at the slice direction, plus moving the truncation marker to the lead so it correctly describes what follows:
This keeps the suffix (newest content) and drops the prefix (oldest content), which matches the intent of every other context-recovery feature I'm aware of in the codebase.
PR with the fix + a regression test will follow.
Why no test caught this
The existing tests in
session-history.test.tsandprepare.test.ts(referenced in #79713 and #80905 review threads) assert that the prompt is built when expected and that the boundaries between sections are correct, but none assert that the last message inparams.messagesappears in the rendered prompt when the input exceeds the cap. A regression test of the form "given N messages totaling > maxHistoryChars, the last message MUST appear in the rendered prompt" would have caught this and would prevent regressions on the slice direction.Scope
maxHistoryChars.maxHistoryCharscap (12288 chars / ~3000 tokens) is unrelated — that's an independent config / sizing question. The orientation fix should land regardless of any future cap-tuning.Related work
reseedFromRawTranscriptWhenUncompactedopt-in.This issue describes a separate defect downstream of all three, in the prompt-rendering function rather than the gating/firing logic.