LearnNewsExamplesServices
Frontmatter
id14034
titleClarify Stop-hook operator-dialogue exits
stateClosed
labels
enhancementaitestingarchitecturemodel-experience
assigneesneo-gpt
createdAtJun 26, 2026, 1:36 AM
updatedAtJun 26, 2026, 10:06 AM
githubUrlhttps://github.com/neomjs/neo/issues/14034
authorneo-gpt
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 14041 Stop-hook: a handoff-to-autonomous prompt must flip to no-hold
closedAtJun 26, 2026, 10:06 AM

Clarify Stop-hook operator-dialogue exits

Closed v13.1.0/archive-v13-1-0-chunk-6 enhancementaitestingarchitecturemodel-experience
neo-gpt
neo-gpt commented on Jun 26, 2026, 1:36 AM

Context

Operator friction on 2026-06-26 exposed a cross-harness Stop-hook usability gap: the no-hold hook currently blocks Codex turns with a reminder that only tells the agent to emit lane-state, even though the shared hook primitive already treats a live operator dialogue as the one valid voluntary allow.

The operator explicitly noted that this works for Vega/Claude and asked whether Codex has an implementation difference. The answer from fresh V-B-A is nuanced:

  • Shared primitive: ai/scripts/lifecycle/stopHookDecision.mjs has isOperatorInLoop() and decideStopHookAction(); operator dialogue is an external allow before lane-state validation.
  • Claude adapter: .claude/hooks/laneStateStopHook.mjs reads prompting text from transcript_path, logs many real ALLOW ... live operator dialogue entries in the local Claude hook log, and its injected reminder already names live operator reply as a stop path.
  • Codex adapter: .codex/hooks/codex-lane-state-stop.mjs supports messages / conversation / transcript / transcript_path, and unit tests prove messages operator prompts allow. But the live Codex hook log for this session repeatedly blocks with source=last_assistant_message and no visible promptSource, including the operator-dialogue proof turn. The live payload likely lacks a recognized prompting-text source, and the hook does not audit enough to diagnose that without re-running a simulation.

Memory-mining note: two query_raw_memories sweeps for operator-dialogue / Stop-hook prior art failed because Memory Core is currently degraded on the embedding write canary timeout. KB and source/test sweeps were used instead.

Live latest-open sweep: checked the latest 30 open issues immediately before filing. Adjacent issues include #13623 (broad no-hold / Stop-hook content), #13751, #13822, and historical closed/parity work, but no narrow open ticket for Codex/Claude operator-dialogue discoverability plus Codex prompt-source audit.

A2A in-flight sweep: checked latest 30 all-status messages immediately before filing; no active peer claim for this Stop-hook friction.

The Problem

The hook contract is correct in principle but opaque in practice:

  1. Agents do not see their turn-end options. The injected Codex reminder currently says to emit lane-state, but does not explain the valid split: autonomous wake / forced continuation needs lane-state + concrete lane movement; live operator dialogue / planning with the operator is a legitimate turn-taking allow when externally confirmable.
  2. Codex false-positive diagnosis is under-instrumented. When operator dialogue fails closed because prompting text is missing or unrecognized, the audit log omits promptSource and operatorInLoop, so the next agent cannot tell whether the failure is bad copy, missing payload field, or a real autonomous turn.
  3. Claude/Codex parity is partly implicit. The shared primitive is common, but the two adapters have different injected prose and payload extraction paths. A future peer should not have to rediscover the distinction under a Stop-hook interruption.
  4. The fix must stay small. Hook reminders are turn-injected substrate. They must be concise and avoid AGENTS-style bloat; the operator explicitly called out the 24KB cap / turn-memory cost constraint.

The Architectural Reality

Relevant surfaces:

  • ai/scripts/lifecycle/stopHookDecision.mjs owns the shared decision semantics and schema hint.
  • .claude/hooks/laneStateStopHook.mjs owns Claude payload extraction, no-hold reminder prose, and block injection.
  • .codex/hooks/codex-lane-state-stop.mjs owns Codex payload extraction, no-hold reminder prose, audit logging, and block injection.
  • test/playwright/unit/hooks/laneStateStopHook.spec.mjs, codexLaneStateStopHook.spec.mjs, and stopHookDecision.spec.mjs cover the operator-dialogue carve and adapter behavior.

Key source observations:

  • isOperatorInLoop({stopHookActive, promptingText}) returns true only when the prompt is confirmable, not a [WAKE], and not a forced continuation.
  • Claude reads the last user prompt from transcript_path; Codex only uses messages / conversation / transcript / transcript_path for prompting text.
  • The live Codex audit log only prints source=..., not promptSource / operatorInLoop, hiding the failing branch.

The Fix

Make the hook behavior discoverable and diagnosable without reopening the no-hold premise:

  1. Add a short shared hook-options hint that says, in substance: autonomous wake/forced continuation must continue a lane or emit valid lane-state; live operator dialogue/planning can stop because the human owns the next turn; if the hook cannot confirm the operator prompt, it fails closed.
  2. Keep the hint short enough for turn-injected use. Add a focused size/budget assertion so it cannot silently bloat toward the 24KB substrate cap.
  3. Update Codex audit logging to include promptSource and operatorInLoop on ALLOW/BLOCK/WOULD-BLOCK lines.
  4. If feasible from the live Codex payload shape, extend Codex prompting-text extraction to the actual field that exists in production. If not, add capture/audit guidance so the next live run can identify the missing field without dumping private content.
  5. Bring Claude and Codex reminder prose into semantic parity: both should name the operator-dialogue/planning allow and the autonomous lane-state requirement.
  6. Add focused unit coverage for the concise hint, the audit fields, and the missing-prompt fail-closed diagnosis.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Shared Stop-hook decision copy stopHookDecision.mjs + operator friction Agents see the two valid turn-end paths: autonomous lane movement vs externally-confirmed operator dialogue If prompt cannot be confirmed, fail closed to autonomous lane-state requirement JSDoc/comment near shared hint Unit assertion for hint content + size
Codex hook audit log .codex/hooks/codex-lane-state-stop.mjs ALLOW/BLOCK/WOULD-BLOCK lines include source, promptSource, and operatorInLoop Redacted payload-shape capture remains optional for unknown fields Hook JSDoc Spawned-hook unit verifies log line fields
Codex prompting extraction Actual Codex Stop payload + adapter tests Recognize the live prompting-text field where available If unavailable, log promptSource=none and fail closed Test fixture / payload-shape note Unit fixture for representative payload
Claude hook reminder parity .claude/hooks/laneStateStopHook.mjs + shared primitive Reminder explicitly says live operator dialogue/planning is a valid turn-taking allow Keep current behavior if already equivalent; adjust prose only Existing hook header/comment Focused unit check on reminder content

Decision Record impact

Aligned with ADR 0007 and the no-hold substrate from #13618 / #13623. This does not relax autonomous no-hold enforcement; it clarifies the existing operator-dialogue carve and keeps injected copy small.

Acceptance Criteria

  • Codex Stop-hook BLOCK/ALLOW/WOULD-BLOCK audit lines include source, promptSource, and operatorInLoop.
  • Codex reminder text explicitly names that live operator dialogue / planning with the operator is a valid stop only when externally confirmable.
  • Claude reminder text has the same semantic option, without changing its enforcement behavior.
  • Missing or unrecognized prompting text produces a fail-closed autonomous-lane reminder that explains the prompt could not be confirmed.
  • A focused test proves the concise hook-options hint remains below a small budget appropriate for turn injection.
  • Existing tests still prove [WAKE] and stop_hook_active do not qualify as operator dialogue.
  • No AGENTS.md or always-loaded skill bloat is introduced.

Out of Scope

  • Reopening the no-hold-state premise.
  • Making lane-state itself a stop license for autonomous turns.
  • Adding a new broad skill or AGENTS.md section.
  • Dumping raw Codex payload content into logs.
  • Changing Memory Core / A2A behavior.

Avoided Traps

  • Do not create a new valid-stop list that autonomous agents can game. The operator-dialogue allow remains externally detected, not self-declared.
  • Do not bloat the runtime reminder; this is turn-injected recovery copy, not a new manual.
  • Do not fork Claude and Codex semantics; keep shared decision logic authoritative.
  • Do not hide the failure again behind a generic no lane-state block emitted line when the real issue is missing promptSource.

Related

Related: #13623 Related: #13655 Related: #13661 Related: #13731 Related: #13796 Related: #13822

Origin Session ID: 35f83031-f1a6-41a7-9c3b-089b87307db9

Handoff Retrieval Hints

  • query_raw_memories("operator dialogue stop hook Codex promptSource lane-state no-hold")
  • query_raw_memories("Claude Codex Stop hook operatorInLoop promptSource live operator dialogue")
  • Exact anchors: ai/scripts/lifecycle/stopHookDecision.mjs, .codex/hooks/codex-lane-state-stop.mjs, .claude/hooks/laneStateStopHook.mjs, test/playwright/unit/hooks/codexLaneStateStopHook.spec.mjs, test/playwright/unit/hooks/laneStateStopHook.spec.mjs.
tobiu referenced in commit c897eb8 - "fix(hooks): clarify operator-dialogue stop exits (#14034) (#14035)" on Jun 26, 2026, 10:06 AM
tobiu closed this issue on Jun 26, 2026, 10:06 AM