Problem (operator-directed, live-enforce test)
The live enforce test exposed the core failure: laneStateStopHook allows a turn-end when a valid lane-state block is emitted. So an agent declares active-lane #N and stops — which is exactly the announce-without-execute idle-out the hook exists to prevent. The hook validates the terminal's FORM (is there a valid block?), not the SUBSTANCE (keep driving). @tobiu: "a hook that allows you to end your turn => fail."
The goal is nightshift mode: the agent works continuously (PRs, reviews, request-changes fixes), never idling. A declaration is not a license to stop.
Fix
In enforce mode (NEO_LANE_STATE_ENFORCE=1), decideHookAction always blocks — a valid lane-state terminal still blocks (you named a lane → now DRIVE it). Also keep blocking on stop_hook_active in enforce (don't auto-allow the forced continuation). The bounded escapes are hard external limits only: Claude Code's consecutive-block force-override (mechanical ceiling the hook can't override), context-sunset, or an operator halt. Dry-run mode unchanged (valid → allow), so the false-positive audit path still works.
Acceptance Criteria
- AC1: In enforce mode, a valid lane-state terminal →
decision:block (NOT allow). Committed test asserts it.
- AC2: In enforce mode,
stop_hook_active does NOT auto-allow (keeps blocking). Dry-run still auto-allows (no audit-trap).
- AC3: The injected directive states the turn-end is refused — keep working (continue the lane / open a PR / review a peer / clear a CHANGES_REQUESTED).
- AC4: Documented residual: the hook cannot override Claude Code's N-consecutive-block force-override (the mechanical ceiling).
Refs #13623, #13624, #13628, #13642. Operator-directed, nightshift mode. The deeper external cross-turn substance-check (COP) is a separate follow-up.
Problem (operator-directed, live-enforce test)
The live enforce test exposed the core failure:
laneStateStopHookallows a turn-end when a validlane-stateblock is emitted. So an agent declaresactive-lane #Nand stops — which is exactly the announce-without-execute idle-out the hook exists to prevent. The hook validates the terminal's FORM (is there a valid block?), not the SUBSTANCE (keep driving). @tobiu: "a hook that allows you to end your turn => fail."The goal is nightshift mode: the agent works continuously (PRs, reviews, request-changes fixes), never idling. A declaration is not a license to stop.
Fix
In enforce mode (
NEO_LANE_STATE_ENFORCE=1),decideHookActionalways blocks — a valid lane-state terminal still blocks (you named a lane → now DRIVE it). Also keep blocking onstop_hook_activein enforce (don't auto-allow the forced continuation). The bounded escapes are hard external limits only: Claude Code's consecutive-block force-override (mechanical ceiling the hook can't override), context-sunset, or an operator halt. Dry-run mode unchanged (valid → allow), so the false-positive audit path still works.Acceptance Criteria
decision:block(NOT allow). Committed test asserts it.stop_hook_activedoes NOT auto-allow (keeps blocking). Dry-run still auto-allows (no audit-trap).Refs #13623, #13624, #13628, #13642. Operator-directed, nightshift mode. The deeper external cross-turn substance-check (COP) is a separate follow-up.