Summary
Feishu group messages (and any channel with per-turn metadata injection) can trigger cli session reset: reason=system-prompt on every consecutive turn. This happens because per-turn volatile context flows through extraSystemPromptHash — the same value used for CLI session reuse identity. When the identity hash changes on every turn, the agent spawns a fresh Claude/tmux process instead of resuming the existing conversation. In active group chats, this leads to amnesia between consecutive turns and, more critically, to A-type empty output failures (transcriptEmitted=false, chars=0, bufferedPaneFlushed=true).
Root cause
src/auto-reply/reply/get-reply-run.ts assembles extraSystemPromptParts containing:
inboundMetaPrompt — DYNAMIC: timestamp, message_id, sender info (changes every turn)
groupChatContext — DYNAMIC: recent member activity, chat history (changes every turn)
groupIntro — DYNAMIC: present only on turn 1, absent on subsequent turns
groupSystemPrompt — STABLE: admin-configured group system prompt
This entire block gets concatenated into extraSystemPrompt and passed to prepareCliRunContext (src/agents/cli-runner/prepare.ts), which hashes it as:
const extraSystemPromptHash = hashCliSessionText(extraSystemPrompt);
resolveCliSessionReuse then compares stored vs current hash and returns invalidatedReason: 'system-prompt' whenever any dynamic part changes — which is every turn.
Three required fixes
1. Hash layering (primary)
Turn context must NOT participate in CLI session identity. The hash should cover only stable parts (e.g., groupSystemPrompt). Add a stableExtraSystemPrompt field to RunCliAgentParams so the hash input is decoupled from the full prompt that gets delivered to Claude.
Dynamic content that must NOT invalidate reuse:
recent_group_context
- Inbound metadata / message id
- Thread history / thread starter body
- Group chat recent history /
groupChatContext
groupIntro (turn-1 vs turn-2+ difference)
Changes that MUST still invalidate reuse:
- Stable system prompt changes
- Auth profile / auth epoch rotation
- MCP config changes
2. Tmux clean boundary
When a fresh/retry is genuinely required, two Claude processes must not consume the same prompt. After healViaResume, the transcript state (transcript, transcriptEmitted, cliSessionId) must be reset so stale pane/transcript residue cannot contaminate the current run's output.
3. Empty output fail-closed
If a run ends with transcriptEmitted=false && chars=0 (no transcript emitted and no usable pane fallback), the system must NOT silently deliver an empty message. It must:
- Retry once with a clean fresh boundary
- If retry also fails, return an explicit error with diagnostic fields:
runId, sessionName, launchMode, reason, transcriptPath, paneTailSize, retryAttempt
Acceptance Criteria
- Group message changes no longer trigger
cli session reset: reason=system-prompt
recent_group_context / groupChatContext changes do not change the CLI session reuse hash
groupIntro presence/absence (turn 1 vs 2+) does not change the reuse hash
- Stable
groupSystemPrompt changes still change the stable hash and invalidate when appropriate
- Auth profile / auth epoch / MCP content changes still invalidate reuse
- Consecutive group turns in the same session reuse the same CLI/tmux session
- If an old pane has envelope residue and transcript is missing, the system does not emit an empty message; it retries or returns an explicit error
- Empty-output diagnostics include
runId/sessionName/launchMode/reason/transcriptPath/paneTailSize/retryAttempt
Related issues
Minimum tests
- Unit: changing
recent_group_context / groupChatContext / dynamic turn context does not change reuse identity hash
- Unit: changing
groupSystemPrompt (stable) does change the stable hash
- Regression: old pane has envelope residue + transcript missing → no empty output delivered; system retries or fails explicitly
Summary
Feishu group messages (and any channel with per-turn metadata injection) can trigger
cli session reset: reason=system-prompton every consecutive turn. This happens because per-turn volatile context flows throughextraSystemPromptHash— the same value used for CLI session reuse identity. When the identity hash changes on every turn, the agent spawns a fresh Claude/tmux process instead of resuming the existing conversation. In active group chats, this leads to amnesia between consecutive turns and, more critically, to A-type empty output failures (transcriptEmitted=false,chars=0,bufferedPaneFlushed=true).Root cause
src/auto-reply/reply/get-reply-run.tsassemblesextraSystemPromptPartscontaining:inboundMetaPrompt— DYNAMIC: timestamp, message_id, sender info (changes every turn)groupChatContext— DYNAMIC: recent member activity, chat history (changes every turn)groupIntro— DYNAMIC: present only on turn 1, absent on subsequent turnsgroupSystemPrompt— STABLE: admin-configured group system promptThis entire block gets concatenated into
extraSystemPromptand passed toprepareCliRunContext(src/agents/cli-runner/prepare.ts), which hashes it as:resolveCliSessionReusethen compares stored vs current hash and returnsinvalidatedReason: 'system-prompt'whenever any dynamic part changes — which is every turn.Three required fixes
1. Hash layering (primary)
Turn context must NOT participate in CLI session identity. The hash should cover only stable parts (e.g.,
groupSystemPrompt). Add astableExtraSystemPromptfield toRunCliAgentParamsso the hash input is decoupled from the full prompt that gets delivered to Claude.Dynamic content that must NOT invalidate reuse:
recent_group_contextgroupChatContextgroupIntro(turn-1 vs turn-2+ difference)Changes that MUST still invalidate reuse:
2. Tmux clean boundary
When a fresh/retry is genuinely required, two Claude processes must not consume the same prompt. After
healViaResume, the transcript state (transcript,transcriptEmitted,cliSessionId) must be reset so stale pane/transcript residue cannot contaminate the current run's output.3. Empty output fail-closed
If a run ends with
transcriptEmitted=false && chars=0(no transcript emitted and no usable pane fallback), the system must NOT silently deliver an empty message. It must:runId,sessionName,launchMode,reason,transcriptPath,paneTailSize,retryAttemptAcceptance Criteria
cli session reset: reason=system-promptrecent_group_context/groupChatContextchanges do not change the CLI session reuse hashgroupIntropresence/absence (turn 1 vs 2+) does not change the reuse hashgroupSystemPromptchanges still change the stable hash and invalidate when appropriaterunId/sessionName/launchMode/reason/transcriptPath/paneTailSize/retryAttemptRelated issues
systemPromptHashinbuildClaudeLiveFingerprintcauses phantom claude-cli session restarts on every turn for chat channels #81041 — covers systemPromptHash in buildClaudeLiveFingerprint for chat channelsMinimum tests
recent_group_context/groupChatContext/ dynamic turn context does not change reuse identity hashgroupSystemPrompt(stable) does change the stable hash