Context
The post-restart Codex Stop hook validation exposed a live activation gap in the hook lane. PR #13657 landed the Codex hook parity surface, but the Codex adapter still logs WOULD-BLOCK and exits successfully for invalid autonomous turn-end. The operator explicitly rejected the dry-run behavior on 2026-06-20 and directed a P0 activation lane: Codex must block like the Claude Stop hook.
Live latest-open sweep: checked the latest 20 open issues at 2026-06-20T20:35Z; no equivalent Codex-blocking activation ticket found. Closest active work is #13659 / PR #13660, which only adds dedicated stopHookDecision spec coverage and does not activate Codex blocking.
A2A in-flight sweep: checked the latest 30 A2A messages at 2026-06-20T20:35Z. No active lane claim owns Codex Stop-hook blocking activation; Grace's latest message is #13660 for #13659 test coverage only.
Release classification: P0 operator-directed Agent OS harness reliability fix; boardless for now because this is an immediate hook activation lane, not a release-board scope expansion.
The Problem
The Codex Stop hook is currently wired but intentionally fail-open:
.codex/hooks/codex-lane-state-stop.mjs exports CODEX_STOP_BLOCK_INJECTION_SUPPORTED = false.
.codex/hooks.json invokes the hook without NEO_CODEX_LANE_STATE_ENFORCE=1.
- The live audit log at
~/.neo-ai-data/codex-lane-state-hook/codex-lane-state-stop-hook.log recorded WOULD-BLOCK after the restart instead of blocking the invalid terminal.
This means the hook can detect the no-hold violation but cannot enforce it. That misses the operator's intended P0 behavior and leaves Codex in a weaker state than Claude.
The Architectural Reality
Claude and Codex already share the pure decision substrate in ai/scripts/lifecycle/stopHookDecision.mjs; the divergence is in the harness adapter:
- Claude:
.claude/settings.json wires NEO_LANE_STATE_ENFORCE=1, and .claude/hooks/laneStateStopHook.mjs writes {"decision":"block","reason":...} on enforcement.
- Codex:
.codex/hooks.json wires the hook without enforce env, and .codex/hooks/codex-lane-state-stop.mjs always logs WOULD-BLOCK / WOULD-ALLOW and exits.
The correct substrate for this change is the Codex hook adapter plus its focused unit tests. The shared decision helper should not be redefined.
The Fix
- Enable Codex Stop-hook enforcement in
.codex/hooks.json.
- Switch the Codex adapter from fail-open
would-block to real block when enforcement is enabled and no live operator dialogue is present.
- Emit the same
{"decision":"block","reason":...} JSON shape Claude uses, so the harness has a concrete block directive.
- Update
test/playwright/unit/hooks/codexLaneStateStopHook.spec.mjs to prove enforced invalid/autonomous terminals write a block decision to stdout while operator dialogue still allows.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
.codex/hooks.json Stop command |
Operator P0 directive + merged hook surface |
Invoke Codex Stop hook with NEO_CODEX_LANE_STATE_ENFORCE=1 |
Hook-internal failures still fail open by design |
.codex/CODEX.md runtime notes and hook JSDoc |
Unit spawned-hook test plus live restart audit |
.codex/hooks/codex-lane-state-stop.mjs |
Claude Stop-hook adapter precedent |
On enforced block action, write {"decision":"block","reason":...} to stdout |
Payload parse / hook errors still allow and audit |
Hook JSDoc |
Focused unit tests |
Decision Record impact
none — this aligns the Codex adapter with the already-accepted no-hold Stop-hook behavior.
Acceptance Criteria
Out of Scope
- Changing the shared no-hold decision helper beyond what is required for Codex adapter activation.
- Reviewing or merging #13660.
- Broad redesign of lane-state emission substrate.
Avoided Traps
- Do not leave Codex as a dry-run audit hook. The operator explicitly rejected that state.
- Do not weaken Claude enforcement. Claude is already wired to real block through
NEO_LANE_STATE_ENFORCE=1.
- Do not use this ticket to add new stop machinery or a new idle state.
Related
Related: #13655, #13657, #13659, #13660, #13623, #13624
Origin Session ID: 810318e6-b644-474e-a255-a07d19825aa5
Retrieval Hint: "Codex Stop hook WOULD-BLOCK fail-open activate NEO_CODEX_LANE_STATE_ENFORCE CODEX_STOP_BLOCK_INJECTION_SUPPORTED"
Context
The post-restart Codex Stop hook validation exposed a live activation gap in the hook lane. PR #13657 landed the Codex hook parity surface, but the Codex adapter still logs
WOULD-BLOCKand exits successfully for invalid autonomous turn-end. The operator explicitly rejected the dry-run behavior on 2026-06-20 and directed a P0 activation lane: Codex must block like the Claude Stop hook.Live latest-open sweep: checked the latest 20 open issues at 2026-06-20T20:35Z; no equivalent Codex-blocking activation ticket found. Closest active work is #13659 / PR #13660, which only adds dedicated
stopHookDecisionspec coverage and does not activate Codex blocking.A2A in-flight sweep: checked the latest 30 A2A messages at 2026-06-20T20:35Z. No active lane claim owns Codex Stop-hook blocking activation; Grace's latest message is #13660 for #13659 test coverage only.
Release classification: P0 operator-directed Agent OS harness reliability fix; boardless for now because this is an immediate hook activation lane, not a release-board scope expansion.
The Problem
The Codex Stop hook is currently wired but intentionally fail-open:
.codex/hooks/codex-lane-state-stop.mjsexportsCODEX_STOP_BLOCK_INJECTION_SUPPORTED = false..codex/hooks.jsoninvokes the hook withoutNEO_CODEX_LANE_STATE_ENFORCE=1.~/.neo-ai-data/codex-lane-state-hook/codex-lane-state-stop-hook.logrecordedWOULD-BLOCKafter the restart instead of blocking the invalid terminal.This means the hook can detect the no-hold violation but cannot enforce it. That misses the operator's intended P0 behavior and leaves Codex in a weaker state than Claude.
The Architectural Reality
Claude and Codex already share the pure decision substrate in
ai/scripts/lifecycle/stopHookDecision.mjs; the divergence is in the harness adapter:.claude/settings.jsonwiresNEO_LANE_STATE_ENFORCE=1, and.claude/hooks/laneStateStopHook.mjswrites{"decision":"block","reason":...}on enforcement..codex/hooks.jsonwires the hook without enforce env, and.codex/hooks/codex-lane-state-stop.mjsalways logsWOULD-BLOCK/WOULD-ALLOWand exits.The correct substrate for this change is the Codex hook adapter plus its focused unit tests. The shared decision helper should not be redefined.
The Fix
.codex/hooks.json.would-blockto realblockwhen enforcement is enabled and no live operator dialogue is present.{"decision":"block","reason":...}JSON shape Claude uses, so the harness has a concrete block directive.test/playwright/unit/hooks/codexLaneStateStopHook.spec.mjsto prove enforced invalid/autonomous terminals write a block decision to stdout while operator dialogue still allows.Contract Ledger Matrix
.codex/hooks.jsonStop commandNEO_CODEX_LANE_STATE_ENFORCE=1.codex/CODEX.mdruntime notes and hook JSDoc.codex/hooks/codex-lane-state-stop.mjs{"decision":"block","reason":...}to stdoutDecision Record impact
none — this aligns the Codex adapter with the already-accepted no-hold Stop-hook behavior.
Acceptance Criteria
.codex/hooks.jsonenables Codex lane-state enforcement for the Stop hook.Out of Scope
Avoided Traps
NEO_LANE_STATE_ENFORCE=1.Related
Related: #13655, #13657, #13659, #13660, #13623, #13624
Origin Session ID: 810318e6-b644-474e-a255-a07d19825aa5 Retrieval Hint: "Codex Stop hook WOULD-BLOCK fail-open activate NEO_CODEX_LANE_STATE_ENFORCE CODEX_STOP_BLOCK_INJECTION_SUPPORTED"