Skip to content

bug: per-turn dynamic context bleeds into extraSystemPromptHash, triggering false cli session reset + A-type empty output #82812

Description

@mylukin

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:

  1. Retry once with a clean fresh boundary
  2. If retry also fails, return an explicit error with diagnostic fields: runId, sessionName, launchMode, reason, transcriptPath, paneTailSize, retryAttempt

Acceptance Criteria

  1. Group message changes no longer trigger cli session reset: reason=system-prompt
  2. recent_group_context / groupChatContext changes do not change the CLI session reuse hash
  3. groupIntro presence/absence (turn 1 vs 2+) does not change the reuse hash
  4. Stable groupSystemPrompt changes still change the stable hash and invalidate when appropriate
  5. Auth profile / auth epoch / MCP content changes still invalidate reuse
  6. Consecutive group turns in the same session reuse the same CLI/tmux session
  7. 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
  8. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions