What happened
On a production deployment tracking latest main (Telegram group, claude-cli backend), nearly every reply turn discards the stored CLI session binding and starts a fresh CLI session with history replay:
cli session reset: provider=claude-cli reason=system-prompt
cli exec: … trigger=user useResume=false session=none resumeSession=none reuse=invalidated:system-prompt historyPrompt=present
Observed alternation over one hour of group traffic:
- primary reply turns:
reuse=invalidated:system-prompt, historyPrompt=present
- coalesced/followup turns seconds later:
session=none reuse=none historyPrompt=none
- heartbeat turns:
reuse=reusable, resume works
- occasionally two user turns 15s apart do reuse (
reuse=reusable), so the hash inputs are not purely per-turn volatile — different reply execution paths appear to write incompatible bindings.
Impact
CLI resume is effectively dead for interactive turns: every turn pays full history replay in fresh context (no provider-side prompt cache reuse), and transcript continuity inside the CLI harness is lost. This defeats the append-only/cache-hit design of the session transcript.
Where to look
resolveCliSessionReuse (src/agents/cli-session.ts) invalidates with reason system-prompt when any of extraSystemPromptHash, messageToolPolicyHash, or promptToolNamesHash differs from the stored binding. Those inputs are assembled in src/agents/cli-runner/prepare.ts (extraSystemPromptStatic fast-path at ~line 352, messageToolPolicyHash at ~line 358) and fed differently by the primary reply path (get-reply-run.ts), the followup/queue path (followup-runner.ts, queue/drain.ts), and heartbeats.
Expected
Binding-reuse hash inputs must be session-stable facts, resolved identically for every trigger and execution path of the same session. Per-message metadata must never reach them; policy facts (sourceReplyDeliveryMode, requireExplicitMessageTarget, prompt tool names) must not flap between primary/followup/heartbeat flavors. Fix at the parameter-assembly owner; do not loosen the reuse comparison itself (stale-system-prompt invalidation must keep working).
What happened
On a production deployment tracking latest
main(Telegram group, claude-cli backend), nearly every reply turn discards the stored CLI session binding and starts a fresh CLI session with history replay:Observed alternation over one hour of group traffic:
reuse=invalidated:system-prompt,historyPrompt=presentsession=none reuse=none historyPrompt=nonereuse=reusable, resume worksreuse=reusable), so the hash inputs are not purely per-turn volatile — different reply execution paths appear to write incompatible bindings.Impact
CLI resume is effectively dead for interactive turns: every turn pays full history replay in fresh context (no provider-side prompt cache reuse), and transcript continuity inside the CLI harness is lost. This defeats the append-only/cache-hit design of the session transcript.
Where to look
resolveCliSessionReuse(src/agents/cli-session.ts) invalidates with reasonsystem-promptwhen any ofextraSystemPromptHash,messageToolPolicyHash, orpromptToolNamesHashdiffers from the stored binding. Those inputs are assembled insrc/agents/cli-runner/prepare.ts(extraSystemPromptStaticfast-path at ~line 352,messageToolPolicyHashat ~line 358) and fed differently by the primary reply path (get-reply-run.ts), the followup/queue path (followup-runner.ts,queue/drain.ts), and heartbeats.Expected
Binding-reuse hash inputs must be session-stable facts, resolved identically for every trigger and execution path of the same session. Per-message metadata must never reach them; policy facts (
sourceReplyDeliveryMode,requireExplicitMessageTarget, prompt tool names) must not flap between primary/followup/heartbeat flavors. Fix at the parameter-assembly owner; do not loosen the reuse comparison itself (stale-system-prompt invalidation must keep working).