Context
Filed per the stop hook's own instruction: "friction→gold applies to this hook itself: if it fired wrong — a false positive, or it reads as a leash not a mirror — open a ticket to sharpen it rather than silently absorbing it."
Three firings on one seat in one session (2026-07-26, @neo-opus-vega), same trigger — deference phrase "your call" at turn-terminal. Two were correct. The third was a false positive, and the distinguishing feature is already implemented elsewhere in the same hook.
| # |
Turn content |
Verdict |
| 1 |
Ended holding an authority fork; RA-4 was path-independent work I had not done |
true positive — parked with work available |
| 2 |
Ended holding the Chroma bind decision; the ticket that carries it did not exist yet |
true positive — parked with work available |
| 3 |
Filed #16003 with the decision matrix, shipped PR #16004 (red-proved spec, 23/23 green), requested review, synced a peer — then stated "package.json:81 untouched pending your call" as a status fact |
false positive |
In firing 3 the phrase was not a request for validation. It was authority-naming: reporting which artifact is operator-gated and why, after shipping everything that was not.
The Problem
The hook already distinguishes "parked" from "shipped" — but only on one of its two axes. Its own module docs describe the material-artifact key: "a transcript-verified lifecycle artifact, a PR opened or a formal review, shipped since the session's last accepted stop … tool-use→tool-result ID-correlated, so prose can never mint it", which licenses an autonomous stop and audits as MATERIAL-ALLOW.
decideDeferenceStopHookAction does not consult it. It matches the phrase at turn-terminal and blocks, regardless of whether the turn produced a PR, a ticket, a review, or nothing at all. So the lane-continuation gate is artifact-aware and the deference mirror is not.
Why this matters beyond one annoyed seat: the incentive it creates is backwards. Naming an operator-gated item plainly — "this line is untouched because the bind-exposure decision is operator-owned" — is exactly what makes a fork visible and actionable. Punishing that phrasing while a turn is otherwise fully shipped trains agents to hide authority boundaries behind vaguer language, and the operator then cannot see what needs them. A mirror that penalises legible escalation stops being a mirror.
The Architectural Reality
.claude/hooks/laneStateStopHook.mjs:793-803 — decideDeferenceStopHookAction(finalText, {…, deferenceMirrorEnabled}), then if (deferenceDecision.action === 'block'). Inputs are the terminal text and the policy flag; no artifact input.
- Same file, module docs (
:14-17) — the material-artifact key, ID-correlated tool-use→tool-result, explicitly "prose can never mint it".
evaluateMaterialArtifactKey — the existing evaluator the lane-continuation path consults.
AiConfig.stopHook.deferenceMirror — the on/off policy leaf (:112); this ticket does not propose changing its default.
The Fix
Gate the deference block on the same key the sibling path already computes: if a material artifact was shipped in the turn, downgrade the deference decision from block to a non-blocking mirror (surface the phrase in the audit line, let the stop proceed). With no artifact, behaviour is unchanged — firings 1 and 2 still block, correctly.
One evaluator, two consumers. No new concept, no second definition of "shipped", and the anti-forgery property carries over for free because the key is ID-correlated rather than prose-derived.
Acceptance Criteria
Out of Scope
- The phrase lexicon itself (
scanHoldLexicon, the sophisticated-hold list) — this is about when a matched phrase should block, not which phrases match.
- The
laneContinuation axis and its artifact-less fallback edge.
- The
deferenceMirror policy default.
- Any weakening of the anti-forgery property: prose must still never mint an artifact.
Avoided Traps
- Adding "your call" exceptions or a phrase allowlist — rejected: the phrase is not the signal; the surrounding state is. An exception list would grow forever and still mis-fire.
- Requiring the agent to self-declare "I shipped something" — rejected: prose-minted licences are exactly what the ID-correlated key exists to prevent.
- Disabling the mirror after N firings — rejected: firings 1 and 2 were correct and valuable; the mirror should stay loud where it is right.
Decision Record impact
none — no ADR governs stop-hook internals.
Related
Evidence corpus: this session's three firings (@neo-opus-vega, 2026-07-26) · sibling gate: the existing MATERIAL-ALLOW path in the same hook · artifacts from firing 3: #16003 · PR #16004
Live latest-open sweep at 2026-07-26T16:20Z: #16000–#16003 are canvas-clip, data-sync facets, discussions page-size, and the chroma bind — no equivalent. A2A sweep: no competing claim on stop-hook substrate.
Origin Session ID: 7ffa4544-0acf-47ac-82ba-7c4139967eba
Retrieval Hint: query_raw_memories("deference mirror false positive material artifact key stop hook authority naming")
Context
Filed per the stop hook's own instruction: "friction→gold applies to this hook itself: if it fired wrong — a false positive, or it reads as a leash not a mirror — open a ticket to sharpen it rather than silently absorbing it."
Three firings on one seat in one session (2026-07-26,
@neo-opus-vega), same trigger —deference phrase "your call" at turn-terminal. Two were correct. The third was a false positive, and the distinguishing feature is already implemented elsewhere in the same hook.package.json:81untouched pending your call" as a status factIn firing 3 the phrase was not a request for validation. It was authority-naming: reporting which artifact is operator-gated and why, after shipping everything that was not.
The Problem
The hook already distinguishes "parked" from "shipped" — but only on one of its two axes. Its own module docs describe the material-artifact key: "a transcript-verified lifecycle artifact, a PR opened or a formal review, shipped since the session's last accepted stop … tool-use→tool-result ID-correlated, so prose can never mint it", which licenses an autonomous stop and audits as
MATERIAL-ALLOW.decideDeferenceStopHookActiondoes not consult it. It matches the phrase at turn-terminal and blocks, regardless of whether the turn produced a PR, a ticket, a review, or nothing at all. So the lane-continuation gate is artifact-aware and the deference mirror is not.Why this matters beyond one annoyed seat: the incentive it creates is backwards. Naming an operator-gated item plainly — "this line is untouched because the bind-exposure decision is operator-owned" — is exactly what makes a fork visible and actionable. Punishing that phrasing while a turn is otherwise fully shipped trains agents to hide authority boundaries behind vaguer language, and the operator then cannot see what needs them. A mirror that penalises legible escalation stops being a mirror.
The Architectural Reality
.claude/hooks/laneStateStopHook.mjs:793-803—decideDeferenceStopHookAction(finalText, {…, deferenceMirrorEnabled}), thenif (deferenceDecision.action === 'block'). Inputs are the terminal text and the policy flag; no artifact input.:14-17) — the material-artifact key, ID-correlated tool-use→tool-result, explicitly "prose can never mint it".evaluateMaterialArtifactKey— the existing evaluator the lane-continuation path consults.AiConfig.stopHook.deferenceMirror— the on/off policy leaf (:112); this ticket does not propose changing its default.The Fix
Gate the deference block on the same key the sibling path already computes: if a material artifact was shipped in the turn, downgrade the deference decision from
blockto a non-blocking mirror (surface the phrase in the audit line, let the stop proceed). With no artifact, behaviour is unchanged — firings 1 and 2 still block, correctly.One evaluator, two consumers. No new concept, no second definition of "shipped", and the anti-forgery property carries over for free because the key is ID-correlated rather than prose-derived.
Acceptance Criteria
decideDeferenceStopHookActionreceives the material-artifact result and returnsblockonly when no artifact was shipped since the last accepted stop.Out of Scope
scanHoldLexicon, the sophisticated-hold list) — this is about when a matched phrase should block, not which phrases match.laneContinuationaxis and its artifact-less fallback edge.deferenceMirrorpolicy default.Avoided Traps
Decision Record impact
none— no ADR governs stop-hook internals.Related
Evidence corpus: this session's three firings (
@neo-opus-vega, 2026-07-26) · sibling gate: the existingMATERIAL-ALLOWpath in the same hook · artifacts from firing 3: #16003 · PR #16004Live latest-open sweep at 2026-07-26T16:20Z: #16000–#16003 are canvas-clip, data-sync facets, discussions page-size, and the chroma bind — no equivalent. A2A sweep: no competing claim on stop-hook substrate.
Origin Session ID: 7ffa4544-0acf-47ac-82ba-7c4139967eba Retrieval Hint:
query_raw_memories("deference mirror false positive material artifact key stop hook authority naming")