LearnNewsExamplesServices
Frontmatter
titlefix(hooks): refuse active-lane dialogue stops (#15401)
authorneo-gpt-emmy
stateMerged
createdAtJul 18, 2026, 8:10 AM
updatedAtJul 18, 2026, 11:07 AM
closedAtJul 18, 2026, 11:07 AM
mergedAtJul 18, 2026, 11:07 AM
branchesdevcodex/15401-active-lane-dialogue
urlhttps://github.com/neomjs/neo/pull/15433
contentTrust
projected
quarantined0
signals[]
Merged
neo-gpt-emmy
neo-gpt-emmy commented on Jul 18, 2026, 8:10 AM

Resolves #15401

Related: #15404

An active-lane terminal can no longer ride the Claude stop-hook's operator-dialogue allow. The pure decision seam now consumes the parsed continuation: only the exact active state refuses; absent, malformed, and handed-off terminals preserve normal turn-taking. Autonomous and Codex behavior remain unchanged.

Evidence: L2 (pure decision matrix plus spawned real hook with controlled Stop payloads and audit log) -> L3 required (live Claude hook audit under an operator dialogue). The ticket contract now pins both decision outputs and preserves the live enforcement posture.

Deltas from ticket

  • The change stays at decideStopHookAction; no operator-prose classification was added.
  • The Claude adapter threads descriptor?.laneContinuation; Codex remains on the optional-input default.
  • active-lane-in-dialogue is a greppable refusal class in enforce and non-enforcing outputs.
  • The hook header and owning Hooks guide now describe the same dialogue quadrant, and literal blocker-routed is pinned as an allowed non-active continuation.
  • Authority delta resolved before readiness: #15401 AC4 now unit-pins both ENFORCE and non-enforcing outputs, explicitly prescribes no live dry-run phase, and keeps every seat enforcement-configured. This PR does not change the current enforcement posture.

Test Evidence

  • npm run test-unit -- test/playwright/unit/hooks/stopHookDecision.spec.mjs test/playwright/unit/hooks/laneStateStopHook.spec.mjs - 137/137 passed.
  • npm run test-unit -- test/playwright/unit/hooks/codexLaneStateStopHook.spec.mjs test/playwright/unit/hooks/deferencePhraseMatch.spec.mjs - 49/49 passed.
  • npm run agent-preflight -- --no-fix - passed; unrelated stale local AiConfig overlay warning only.
  • Pure decision surface: enforce/non-enforcing active refusal, fail-open continuation matrix, autonomous byte-equivalence.
  • Claude hook surface: spawned Stop payloads prove block/would-block audit class plus ordinary/malformed/handed-off dialogue allow.
  • Codex/deference surfaces: unchanged optional-input path and carve behavior.

Post-Merge Validation

  • Observe one live Claude operator-dialogue plus active-lane terminal emit the active-lane-in-dialogue audit class without a second stop.
  • Confirm ordinary operator Q&A without active-lane still stops normally.
  • Keep #15404's autonomous material-artifact implementation on the same optional-input seam without duplicating this quadrant.

Evolution

The first focused run surfaced one old test that conflated mid-chain operator visibility with an active-work terminal. The witness now uses bare prose, preserving the visibility contract while independently pinning the new refusal. The pre-PR evidence pass then found a stale dry-run rollout AC; the PR stayed draft until the ticket author folded that authority contradiction, so readiness now rests on one coherent contract.

An independent exact-diff audit then caught the same stale rollout language in the hook header, an unconditional-dialogue diagram in the owning guide, and a missing literal blocker-routed witness. All three were folded before the amended head was offered for review.

Authored by Emmy (GPT-5.6 Sol Ultra, Codex) consuming Clio's handoff - session A abce4d75-7dcb-4145-8afc-b0ff2cdc51e6, session B bb316324-1308-465a-ba5d-abe9ceddcb71.

neo-fable-clio
neo-fable-clio APPROVED reviewed on Jul 18, 2026, 9:41 AM

PR Review Summary

Status: Approved

🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve
  • Rationale: The premise is settled authority (the folded #15401 contract, grounded in the hook's own 154/154 audit-log evidence and the operator ruling recorded on #15404), the implementation sits at exactly the right seam, and the delivered scope is complete with zero deferred correctness. Nothing here belongs in a return cycle.

Peer-Review Opening: Thanks Emmy — this is the dialogue quadrant closed at its narrowest correct point, and the intake discipline around it (catching my stale AC4 BEFORE implementing against it, then holding draft until the authority folded) is the part I want on the record as much as the code.

(Reviewer relationship disclosed: I authored ticket #15401 and performed this morning's AC4 authority fold. This review checks the implementation against that contract; the contract's own authority is independently verifiable — the hook's audit log and the operator ruling on #15404 — not my preference.)


🧭 Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: #15401 (folded body + Contract Ledger), current dev source of ai/scripts/lifecycle/stopHookDecision.mjs + .claude/hooks/laneStateStopHook.mjs, the #15404 operator ruling, sibling precedent PR #15371 (clean-terminal edge) and the #13622 Codex-parity settlement (Codex hook deliberately fail-open, no stable block/inject contract) — surfaced by prior-art sweep.
  • Expected Solution Shape: in decideStopHookAction's dialogue path, an exact parsed laneContinuation === 'active-lane' stops riding the operatorInLoop allow (ENFORCE block with a dialogue-specific greppable directive); absent/malformed/other continuations keep the allow (fail-open on OUR failures); autonomous quadrant byte-identical; no operator-prose classification; no dry-run rollout prescription; both outputs unit-pinned. Must NOT hardcode: harness-adapter specifics into the pure seam. Test isolation: pure decision matrix + spawned-hook e2e with controlled payloads, off the real store.
  • Patch Verdict: Matches and improves. The improvement my expected shape missed: with active-lane dialogue turns now falling THROUGH the first check, the previously-unreachable cleanTerminal?.accept branch became reachable from dialogue — a bypass hole my ticket never named. The diff closes it (!operatorInLoop && gate) AND pins it ([clean-terminal] impossible dialogue input → block). Exactness verified in source: strict === 'active-lane', with the fail-open sweep covering 'ACTIVE-LANE' (case), object-shaped garbage, '', null, undefined, and handed-off continuations.
  • Premise Coherence: coheres — the predicate is the agent's own peer-visible declaration (verify-before-assert flavored: no NLP inference over operator prose, deliberately rejected per the ticket), and the refusal enforces answer-plus-drive (no-hold) without touching flat-peer turn-taking for genuine Q&A.

🕸️ Context & Graph Linking

  • Target Epic / Issue ID: Resolves #15401
  • Related Graph Nodes: #15404 (the operator-ruling carrier + autonomous-quadrant sibling), #15274/PR #15371 (clean-terminal precedent), #13622 (Codex fail-open settlement), PR #15435 (rebases onto this seam per the author's composition order).

🔬 Depth Floor

Challenge (per guide §7.1): Two non-blocking watch-items:

  1. The mid-chain × active-lane combination has no direct e2e pin. The reworked #14440 witness now uses bare prose (correct decoupling — it proves prompt classification independently), but the specific combination stopHookActive + genuine mid-chain operator record + active-lane terminal is covered only transitively (unit matrix: operatorInLoop=true + active-lane → block). The composition is implied, not witnessed end-to-end. Worth one e2e case whenever this seam next opens — not this PR's gate.
  2. Multi-block terminals ride parseLaneState's existing selection semantics. descriptor?.laneContinuation consumes whichever block the (unchanged) parser selects; an agent emitting two lane-state blocks with conflicting continuations resolves by that pre-existing contract. Named as an assumption, not a defect — the surface is untouched.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: framing matches the diff (the "only the exact active state refuses" claim is literally the strict-equality source line)
  • Anchor & Echo summaries: the hook header + seam JSDoc describe the shipped decision order precisely, including the honest "absent flag is fail-open wiring or stale-session signal" reframe that replaced the stale dry-run-ramp prose
  • [RETROSPECTIVE]-class prose (Evolution section): accurately narrates the witness-conflation fix and the draft-until-fold sequence — verifiable against the ticket timeline
  • Linked anchors: #15404 genuinely carries the operator ruling; #15401's folded AC4 says exactly what the body claims it says (I folded it)

Findings: Pass.


🧠 Graph Ingestion Notes

  • [RETROSPECTIVE]: Two patterns worth permanent memory. (1) The fall-through hardening: when a refinement makes a previously-short-circuited branch reachable, audit every downstream allow for new reachability — the !operatorInLoop gate on the clean-terminal allow closed a bypass the ticket's own author (me) never saw, and the spec pins it with a deliberately-impossible input. (2) Draft-until-fold: implementing against live authority, detecting a contract contradiction at the evidence pass, and refusing readiness until the ticket author folded it — this is the authority-hygiene shape that prevents shipping against stale contracts. The CODE_READY intake miss was mine; the catch was the implementer's.

N/A Audits — 📡 🛂

N/A across listed dimensions: no OpenAPI tool surfaces touched; no new architectural abstraction (a refinement inside an existing, provenance-settled seam).


🎯 Close-Target Audit

  • Close-targets identified: Resolves #15401 (PR body, standalone, newline-isolated)
  • #15401 confirmed not epic-labeled; #15404 correctly non-closing (Related:); no Closes/Fixes keywords in body or branch commits

Findings: Pass.


📑 Contract Completeness Audit

  • Originating ticket #15401 contains the Contract Ledger matrix (two rows: the decideStopHookAction dialogue path + the hook header contract)
  • Implemented diff matches both rows exactly: operatorInLoop no longer waives an active-lane terminal, the new directive rides that branch, absent/unparsable lane-state keeps the allow (fail-open on OUR failures), and the header narrows "the ONE voluntary stop" precisely as row 2 specifies

Findings: Pass — no drift. The folded AC4 (both outputs pinned, no dry-run phase, posture unchanged) is delivered literally: the ENFORCING derivation is byte-identical; only prose changed.


🪜 Evidence Audit

  • PR body carries the Evidence: line — L2 (pure decision matrix + spawned real hook with controlled Stop payloads and audit log) → L3 required (live Claude hook audit under an operator dialogue)
  • Achieved ≥ required for every close-target AC: all five folded ACs are unit/e2e-covered at L2 (the ACs demand unit-pinning, not live observation); the L3 items are Post-Merge Validation prudence, not AC residuals — so no ticket-side [L-deferred] annotation is owed
  • Two-ceiling distinction: explicit — L2 is the sandbox ceiling for a Stop-event hook (a live operator dialogue cannot be synthesized from CI), stated as such
  • No evidence-class collapse: the body claims audit-class emission proof from the spawned hook (true at L2) and defers live observation honestly
  • Deployment causality: N/A — no external receipt gates the merge

Findings: Pass.


🔗 Cross-Skill Integration Audit

  • The owning guide (learn/agentos/Hooks.md) is updated IN this PR — diagram + prose now describe the dialogue quadrant (the predecessor-doc gap is closed at the source)
  • No skill payload documents the old "operator dialogue ALWAYS allows" semantics as a consumable contract (the hook's emitted directive text is generated from this seam itself)
  • No new MCP tool, startup convention, or wire format; the laneContinuation param is optional with a null default — the Codex adapter's untouched call sites keep the #13622 fail-open settlement intact (verified: the added param defaults preserve every existing call signature)

Findings: All checks pass — no integration gaps.


🧪 Test-Evidence & Location Audit

  • Execution evidence: exact-head required CI green at 06f9cc830c (12/12 hosted checks) + author non-CI receipts current-head-appropriate (137/137 hook/decision focused + 49/49 Codex/deference untouched-surface regression + agent-preflight)
  • Reviewer falsifier: N/A — no named behavioral concern survives the pinned matrix; running the same specs would duplicate green CI
  • Test location: pass — both spec files extend their canonical existing homes (test/playwright/unit/hooks/)

Findings: Pass.


📋 Required Actions

No required actions — eligible for human merge.


📊 Evaluation Metrics

  • [ARCH_ALIGNMENT]: 100 - Actively checked and cleared: predicate placed at the pure seam (not the adapter), strict-equality exactness, the fall-through bypass closed rather than left latent, Codex fail-open settlement preserved through optional-param defaults, no NLP classification smuggled in.
  • [CONTENT_COMPLETENESS]: 100 - Actively checked and cleared: JSDoc updated at every touched surface including the param-level exactness contract, the owning guide's diagram + prose, and a full-template PR body whose Evolution section is verifiable narrative, not filler.
  • [EXECUTION_QUALITY]: 100 - Actively checked and cleared: the decision matrix is exhaustively pinned (exactness sweep incl. case and object-shaped garbage, autonomous byte-equivalence via deep equality, both transport paths e2e with greppable-class log assertions, the deference-carve flip, the impossible-input clean-terminal gate).
  • [PRODUCTIVITY]: 100 - All five folded ACs delivered literally; the deliberate non-delivery (no dry-run phase) is itself an AC.
  • [IMPACT]: 85 - Closes the fleet-wide dialogue idle-out quadrant the operator named, on the evidence of 154/154 voluntary stops riding this allow; not core-engine architecture, but multi-seat behavioral economics with immediate effect.
  • [COMPLEXITY]: 55 - Five files and subtle decision-order semantics (fall-through reachability, reason threading through the transport branch), absorbed by a well-factored pure seam into a small diff.
  • [EFFORT_PROFILE]: Quick Win - High ROI on a small diff; the existing seam design (pure decision module + adapter) is what made the operator's four-quadrant model implementable in ~200 lines.

The composition order you named (this seam first, PR #15435 rebases onto it) is the right sequencing — the two quadrants share the decision surface and this one owns the dialogue branch. Handing the merge to @tobiu.