Context
PR #15210 (merged 2026-07-16 07:32Z, Resolves #14440) shipped mid-chain operator visibility: extractLatestHumanUserTextFromJsonl walks the transcript for the newest mechanically human-shaped user record, and classifyPromptingContext counts an attested mid-chain dialogue candidate as operatorInLoop. That fix is correct for its fixture corpus — operator messages that START a turn inside a forced-continuation chain land as genuine user-role records and are now rescued.
Operator direction (live session, same day): create the follow-up for the remaining shape.
The Problem
Claude Code delivers mid-TURN operator messages ("The user sent a new message while you were working") as type: 'attachment' records, not user-role records — so the walk cannot see them by construction, and a session whose operator interacts primarily mid-turn keeps looping exactly as before the fix.
Live falsifier (session transcript 90c8bdb2-d5c7-4231-85b3-5271aaf109ec.jsonl, 2026-07-16):
- The operator's mid-turn scope-rulings message exists ONLY as: two
type: 'queue-operation' records (enqueue envelopes, no role) + one type: 'attachment' record carrying the text at record.attachment.prompt (line 407). Zero user-role records carry it.
- Traced consequence on the same session's six consecutive hook fires (forensics on the closed
#14440, comments 4989142471 / 4989282036): at each refused terminal the newest non-meta USER record was a [WAKE] delivery, so midChainOperator stays false and the terminals still BLOCK post-merge — while the operator was actively messaging mid-turn throughout.
- Also verified live, both clean (no action needed):
Stop hook feedback: records are consistently isMeta: true (5/5 — the merged discriminator holds), and <task-notification> deliveries never materialize as user-role records (no false-ALLOW channel).
Same-family review with the full trace: PR #15210 review 4711437374.
The Architectural Reality
.claude/hooks/laneStateStopHook.mjs — extractLatestHumanUserTextFromJsonl filters on (message.role || record.type) === 'user', skips isMeta: true, text-less, and harness-marker records; FIRST remaining candidate decides (newest-candidate rule — this ordering must survive the extension, or stale attachments could leak past fresher [WAKE] boundaries).
ai/scripts/lifecycle/stopHookDecision.mjs — isOperatorDialogueText is the single classification authority for BOTH paths; the extension must route attachment text through it unchanged (no second authority).
- The
promptingTextHumanFiltered attestation contract: the Claude adapter attests its extraction mechanically excluded harness-injected records. Attachment records need the same discipline audit: my live instance carries no isMeta and IS genuine operator prose, but other attachment KINDS (file attachments, pasted content?) may exist — the gate should be presence of attachment.prompt (the queued-user-message field), and a small corpus check across real transcripts should confirm no harness-injected record ever carries attachment.prompt.
The Fix
- Extend the walk (or add a sibling considered in the same backward pass, preserving strict transcript-position ordering) to accept
type === 'attachment' records with a non-empty string attachment.prompt, returning that text as the candidate.
- Classification stays in
isOperatorDialogueText — a [WAKE]-shaped or synthetic attachment (if any exist) fails exactly like a user-record equivalent.
- Fixture corpus: mirror the real record shapes (queue-operation envelope skipped by shape; attachment carrying
attachment.prompt; the line-407 chain). The originating session transcript is available on request as the reference corpus.
- Specs: mid-turn attachment beneath hook-feedback records → found; attachment ordering vs newer
[WAKE] user record → the newer wake wins; attachment.prompt-less attachment records (other kinds) → skipped; end-to-end spawned-hook ALLOW on the attachment chain shape.
Acceptance Criteria
Out of Scope
- Codex adapter semantics (stays fail-closed without attestation, per the merged design).
- Any re-litigation of the merged turn-start rescue (shipped, correct).
Decision Record impact
none (hook substrate; #14440 lineage).
Related
Predecessor: #14440 (closed by PR #15210) · same-family review carrying the trace: PR #15210 review 4711437374 · forensic timeline: #14440 comments 4989142471 / 4989282036.
Live latest-open sweep: checked latest 20 open issues at 2026-07-16 ~07:37Z; no equivalent found. A2A in-flight claim sweep: mailbox clean of overlapping claims at file time. Structure-map gate: N/A — extends existing hook + decision module in place.
Origin Session ID: 75ed6708-c66b-4989-862d-2286e87abbf1
Retrieval Hint: "stop hook attachment prompt mid-turn operator visibility human-filtered walk"
Context
PR #15210 (merged 2026-07-16 07:32Z,
Resolves#14440) shipped mid-chain operator visibility:extractLatestHumanUserTextFromJsonlwalks the transcript for the newest mechanically human-shaped user record, andclassifyPromptingContextcounts an attested mid-chain dialogue candidate asoperatorInLoop. That fix is correct for its fixture corpus — operator messages that START a turn inside a forced-continuation chain land as genuine user-role records and are now rescued.Operator direction (live session, same day): create the follow-up for the remaining shape.
The Problem
Claude Code delivers mid-TURN operator messages ("The user sent a new message while you were working") as
type: 'attachment'records, not user-role records — so the walk cannot see them by construction, and a session whose operator interacts primarily mid-turn keeps looping exactly as before the fix.Live falsifier (session transcript
90c8bdb2-d5c7-4231-85b3-5271aaf109ec.jsonl, 2026-07-16):type: 'queue-operation'records (enqueue envelopes, no role) + onetype: 'attachment'record carrying the text atrecord.attachment.prompt(line 407). Zero user-role records carry it.#14440, comments4989142471/4989282036): at each refused terminal the newest non-meta USER record was a[WAKE]delivery, somidChainOperatorstays false and the terminals still BLOCK post-merge — while the operator was actively messaging mid-turn throughout.Stop hook feedback:records are consistentlyisMeta: true(5/5 — the merged discriminator holds), and<task-notification>deliveries never materialize as user-role records (no false-ALLOW channel).Same-family review with the full trace: PR #15210 review
4711437374.The Architectural Reality
.claude/hooks/laneStateStopHook.mjs—extractLatestHumanUserTextFromJsonlfilters on(message.role || record.type) === 'user', skipsisMeta: true, text-less, and harness-marker records; FIRST remaining candidate decides (newest-candidate rule — this ordering must survive the extension, or stale attachments could leak past fresher[WAKE]boundaries).ai/scripts/lifecycle/stopHookDecision.mjs—isOperatorDialogueTextis the single classification authority for BOTH paths; the extension must route attachment text through it unchanged (no second authority).promptingTextHumanFilteredattestation contract: the Claude adapter attests its extraction mechanically excluded harness-injected records. Attachment records need the same discipline audit: my live instance carries noisMetaand IS genuine operator prose, but other attachment KINDS (file attachments, pasted content?) may exist — the gate should be presence ofattachment.prompt(the queued-user-message field), and a small corpus check across real transcripts should confirm no harness-injected record ever carriesattachment.prompt.The Fix
type === 'attachment'records with a non-empty stringattachment.prompt, returning that text as the candidate.isOperatorDialogueText— a[WAKE]-shaped or synthetic attachment (if any exist) fails exactly like a user-record equivalent.attachment.prompt; the line-407 chain). The originating session transcript is available on request as the reference corpus.[WAKE]user record → the newer wake wins;attachment.prompt-less attachment records (other kinds) → skipped; end-to-end spawned-hook ALLOW on the attachment chain shape.Acceptance Criteria
midChainOperator: true→ ALLOW (end-to-end spawned-hook spec).[WAKE]user record NEWER than an operator attachment keeps the turn autonomous (spec-pinned).attachment.promptnever become candidates (spec-pinned).isOperatorDialogueText— no duplicated gates.attachment.prompt(or the discriminator that excludes those that do).Out of Scope
Decision Record impact
none (hook substrate;
#14440lineage).Related
Predecessor:
#14440(closed by PR #15210) · same-family review carrying the trace: PR #15210 review4711437374· forensic timeline:#14440comments4989142471/4989282036.Live latest-open sweep: checked latest 20 open issues at 2026-07-16 ~07:37Z; no equivalent found. A2A in-flight claim sweep: mailbox clean of overlapping claims at file time. Structure-map gate: N/A — extends existing hook + decision module in place.
Origin Session ID: 75ed6708-c66b-4989-862d-2286e87abbf1
Retrieval Hint: "stop hook attachment prompt mid-turn operator visibility human-filtered walk"