fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars#83117
Conversation
|
Codex review: needs changes before merge. Latest ClawSweeper review: 2026-05-23 21:41 UTC / May 23, 2026, 5:41 PM ET. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
PR Surface View PR surface stats
Summary Reproducibility: yes. at source level. Current main renders chronological messages and prefix-slices over PR rating Rank-up moves:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. Real behavior proof Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge Security Review findings
Review detailsBest possible solution: Land the tail-preserving renderer after every truncating branch reserves marker and separator budget inside the configured history budget, with a focused test for the summary-plus-overflow-tail case. Do we have a high-confidence way to reproduce the issue? Yes, at source level. Current main renders chronological messages and prefix-slices over Is this the best way to solve the issue? No, not quite yet. Tail-slicing recent history and pinning the compaction summary are the right direction, but the post-summary tail-overflow branch still needs marker-budget accounting before merge. Label changes:
Label justifications:
Full review comments:
Overall correctness: patch is incorrect Acceptance criteria:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against fa5c8345f38b. |
10c39df to
e05e70b
Compare
e05e70b to
bcb3f29
Compare
|
ClawSweeper PR egg 🔥 Warming up: real-behavior proof passed; findings, security review, or rank-up moves are still in progress. Hatch commandComment Hatchability rules:
What is this egg doing here?
|
|
Hi @steipete @hclsys @bitloi — this PR has been sitting at ClawSweeper Could one of you take a look when you have a chance? Happy to rebase if it goes stale before merge. |
This comment was marked as low quality.
This comment was marked as low quality.
…oryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157
bcb3f29 to
d027bc1
Compare
ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present.
|
@steipete — addressed the ClawSweeper P2 in All acceptance gates green:
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
🦞👀 Command router queued. I will update this comment with the next step. Re-review progress:
|
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…oryChars (openclaw#83117) * fix(cli-runner): keep recent tail when reseed history exceeds maxHistoryChars `buildCliSessionHistoryPrompt` was prefix-slicing the rendered history, dropping the most recent assistant turns from the reseed prompt. After openclaw#80934 made the Claude-CLI reseed default-on, every Claude-CLI user is exposed to this on session_expired when the rendered transcript exceeds 12288 chars. The truncation marker landed mid-word in real reproductions. Fix: - Tail-slice (keep the recent suffix, drop the older prefix) - Pin the compaction summary as a prefix when present, only cap the post-summary transcript (loadCliSessionReseedMessages deliberately places the summary first) - When the summary alone exceeds maxHistoryChars, head-slice the summary itself to honor the cap; drop the post-summary tail in that case - Move the truncation marker to the lead since what follows is the recent tail, not what was dropped Closes openclaw#83157 * fix(cli-runner): retain recent tail with oversize summaries * fix(cli-runner): cap summary block plus marker against maxHistoryChars ClawSweeper P2 on openclaw#83117 flagged that when `summaryRendered.length` is less than `maxHistoryChars` but `summaryBlock.length` (summary + `\n\n` separator) meets or exceeds it, the `remainingBudget <= 0` arm of `buildCliSessionHistoryPrompt` appends the truncation marker after the already-full summary block. A 199-char rendered summary under a 200-char cap produced a 257-char history block — defeating the cap that prevents reseeding fresh CLI sessions with unexpectedly huge prompts. Fix the budget edge by truncating the summary in this branch as well so `summary + separator + marker` stays within `maxHistoryChars`. The tail still drops (the summary alone consumes the budget) and the marker still leads its own line so the prompt announces what was discarded. Mirrors the existing oversize-summary branch's pattern of head-slicing the summary against an explicit budget that reserves marker + separator. Add a focused regression in `session-history.test.ts` covering exactly the gap the finding called out: `summaryRendered.length < maxHistoryChars` with a non-empty post-summary tail. Asserts the rendered history block stays within `maxHistoryChars` and the truncation marker is present. * fix(cli-runner): keep tail for near-cap summaries --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
Summary
buildCliSessionHistoryPromptinsrc/agents/cli-runner/session-history.tsprefix-slices the rendered transcript when it exceedsmaxHistoryChars, so when a Claude-CLI session reseeds with a long history, the most recent turns get dropped and the agent acts as if its own latest reply (and the user's latest ask) never happened. Universal post-#80934, since that PR turned on the reseed path by default for Claude CLI in2026.5.12.Closes #83157.
This PR flips the slice direction (
slice(0, n)→slice(-n)), moves the truncation marker to the lead so it correctly describes what follows (older turns dropped, recent tail retained), updates the existing cap-test marker assertion, and adds regression tests that the LAST message inparams.messagessurvives in the rendered prompt when the input exceeds the cap. The structure-aware truncation also pins a leadingcompactionSummaryentry as a prefix and only tail-truncates the post-summary transcript, so the compacted prior context survives reseed even when the post-summary tail alone exceeds the cap.session_expiredor other reseed-eligible reason fires mid-conversation; the recovered session immediately loses the most recent context.slice(0, maxHistoryChars).trimEnd()→slice(-maxHistoryChars).trimStart(), plus marker phrasing/position update, structure-aware truncation that pins a leadingcompactionSummaryas a prefix and head-slices the summary itself when it alone exceeds the cap, and three regression tests.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
loadCliSessionReseedMessagesreturning[]; orthogonal)Real behavior proof (required for external PRs)
External-contributor real-environment proof, captured on macOS 15.4 / Node 22 /
2026.5.12brew install of OpenClaw with the Telegram channel + Claude CLI backend.Behavior or issue addressed: when a long-running Claude-CLI session reseeds (e.g.
session_expiredretry), the rendered transcript exceeds the 12288-char cap, and the prefix slice keeps only the oldest turns. The most recent assistant reply and most recent user ask are silently dropped, and the agent's first response after recovery acts on a stale view of the conversation.Real environment tested: OpenClaw
2026.5.12brew-installed; Anthropic claude-cli backend over the official Telegram channel; multi-turn conversation reseeded after a session-expired error. Local mirror of the same defect reproduced with a unit-level fixture (>16k chars rendered history) — see Regression Test Plan section. Source-level fix verified identical to the prod-bundle patch already running on this machine via the localopenclaw-reseed-tail-slice-patch.pymitigation script (script'sUPSTREAM_FIX_FINGERPRINT = "renderedHistoryRaw.slice(-maxHistoryChars)"matches this PR's source).Exact steps or command run after this patch:
git checkout fix/cli-runner/reseed-tail-slicepnpm install(already up-to-date)pnpm build— produceddist/session-history-ad5FiFRu.jsnode scripts/run-vitest.mjs run src/agents/cli-runner/session-history.test.ts— all 36 tests pass across the two project lanes including the three new regressionsnode scripts/run-vitest.mjs run src/agents/cli-runner/session-history.test.ts src/agents/cli-runner/prepare.test.ts— all 82 tests pass across the cli-runner test surfacepnpm tsgo:coreandpnpm tsgo:core:test— both cleanpnpm exec oxfmt --check --threads=1 src/agents/cli-runner/session-history.ts src/agents/cli-runner/session-history.test.ts— cleannode scripts/run-oxlint.mjs src/agents/cli-runner/session-history.ts src/agents/cli-runner/session-history.test.ts— 0 warnings, 0 errors0, messageupstream reseed-tail-slice fix detected in session-history-ad5FiFRu.js (found 'renderedHistoryRaw.slice(-maxHistoryChars)'); no patching needed.Evidence after fix (real session capture, redacted):
Recovery preamble captured from a real Telegram → Claude-CLI reseed before this fix landed (this is what the agent was handed as
<conversation_history>after an upstream session-expired retry).[REDACTED]blocks replace identifiable user-content text; the[OpenClaw reseed history truncated]marker and the mid-word cut-offThimmediately preceding it are the load-bearing details:Total rendered history length: 13568 chars (cap is 12288). The slice kept the first 12288 chars — i.e. the OLDEST turns — and cut the latest assistant reply mid-word
Th[at one ...immediately before the truncation marker. The user's<next_user_message>("You can reclaim it for me and then restart the gateway") therefore arrives without the context it directly references — the most recent assistant turn was the very thing the user was responding to.Captured at
2026-05-17T03:57:21.779Z(UTC).After this fix, the same input produces:
The latest turn is preserved end-to-end; the marker leads, correctly describing that older turns were dropped.
Test runs after fix:
Local mitigation patch self-suppression check:
This is the cleanest acceptance test that the source change is patch-equivalent — the local mitigation script that's been running on this machine since the defect was found will now silently no-op against any release containing this fix.
Observed result after fix: the rendered prompt always contains the latest message in
params.messages. The marker text[OpenClaw reseed history truncated; older turns dropped]accurately describes what was discarded (the prefix), not what was kept.What was not tested: a live OC instance running the freshly-built bundle in production-shape Telegram → Claude-CLI flow with a long enough conversation to trip the cap. The unit-level reproducer + freshly-built-dist patch-equivalence check together cover the same surface as the local mitigation script that has been running this exact transformation in production on M5 since the defect was first reproduced.
Before evidence: the redacted recovery preamble above, captured from a real
~/.claude/projects/.../*.jsonlthread on2026-05-17T03:57:21.779Z.Root Cause
params.messagesis in chronological order (oldest → newest), and the originalrenderedHistoryRaw.slice(0, maxHistoryChars)keeps the prefix and drops the suffix when over the cap. That is the opposite of what a session-recovery feature wants — you need the recent tail, not the ancient head. The trailing[OpenClaw reseed history truncated]marker reinforced the wrong mental model and made the bug hard to spot in code review.buildCliSessionHistoryPrompttest insession-history.test.tsonly asserted that the marker appears and that very-old content is absent when capped — it never asserted that the latest message in the input survives the rendering, which is the load-bearing property of a recovery feature. This PR adds that assertion.2026.5.12). Until then this defect was effectively unreachable, so it never surfaced under earlier tests.Regression Test Plan
src/agents/cli-runner/session-history.test.ts— added threeit(...)cases inside the existingdescribe("buildCliSessionHistoryPrompt", ...)block:it("keeps the most recent turns when rendered history exceeds the cap", ...)— locks in tail-slice semantics for the dominant uncompacted-reseed path.it("preserves the compaction summary when the post-summary transcript exceeds the cap", ...)— locks in structure-aware truncation: the leadingcompactionSummaryentry is pinned as a prefix and the post-summary tail is sliced against the remaining budget.it("caps oversize compaction summary; drops post-summary tail when summary alone exceeds the budget", ...)— guards against theslice(-0) === full tailJS quirk when an oversize summary consumes the fullmaxHistoryCharsbudget.maxHistoryChars, the LAST message's content MUST appear in the rendered prompt; the head-most content MUST NOT appear; the lead truncation marker MUST be present; a leadingcompactionSummaryentry MUST survive even when the post-summary tail alone exceeds the cap; and an oversize summary MUST NOT cause aslice(-0)regression that leaks the entire tail.xblock; a blind tail-slice would drop the summary; a missingslice(-0)guard would leak the full tail) and passes on the fixed code.it("caps rendered reseed history before adding the next user message", ...)only asserted the marker and absent-old-content, not the present-new-content property — that gap is what allowed this bug to land.User-visible / Behavior Changes
[OpenClaw reseed history truncated](trailing) to[OpenClaw reseed history truncated; older turns dropped](leading). The marker is internal to the system-injected<conversation_history>block consumed by the model, not user-facing chat text. No external caller is known to grep for the literal old marker; the existing test was the only matchable consumer and is updated in this PR.Implementation notes
params.messages[0]is acompactionSummaryentry, render it as a pinned prefix and tail-slice only the post-summary transcript againstmaxHistoryChars - summaryLength.maxHistoryChars; the post-summary tail is dropped in that case.Diagram