Context
Friction surfaced live on 2026-06-28 while trying to end an operator-directed Codex turn as operator dialogue/planning. Claude/Grace can stop on this path; Codex kept receiving the Stop-hook no-hold reminder instead.
This is not a request to weaken §no_hold_state. It is the hook's own friction→gold clause firing: when a human operator is actively in dialogue, the hook must provide a stop option that is externally confirmable even when the Codex Stop payload cannot directly expose the prompt text.
The Problem
The current Codex Stop-hook path can fail closed into autonomous/no-hold even for a live operator turn.
Live V-B-A from the current session:
- Claude Stop audit logs show repeated
ALLOW operatorInLoop=true entries for operator-dialogue turns.
- Codex Stop audit logs for the same period show
BLOCK ... promptSource=transcript_path, operatorInLoop=false, including the operator-dialogue attempt.
- Running the current Codex helper against the live Codex transcript returned
userLength: 0, assistantLength: 0, promptSource: transcript_path, operatorInLoop: false.
- Inspecting the live Codex transcript shape showed messages under
record.payload (payload.type="message", payload.role="user"). The Codex JSONL helpers currently inspect only root/message/item-shaped records, so the transcript exists but the prompt and assistant text are not extracted.
That explains today's failure, but the operator highlighted the wider contract: even if a Stop payload or transcript cannot expose the prompt directly, operator dialogue still needs a safe stop path. Otherwise the hook becomes a leash during live human dialogue, exactly the false-positive class the friction→gold clause says to turn into substrate.
The Architectural Reality
- Shared semantics already live in
ai/scripts/lifecycle/stopHookDecision.mjs: live operator dialogue is the only voluntary allow; autonomous [WAKE] / stop-hook continuations must continue driving.
- Harness adapters are still split across
.claude/hooks/laneStateStopHook.mjs and .codex/hooks/codex-lane-state-stop.mjs.
- Codex has
.codex/hooks/codex-context.mjs on UserPromptSubmit, which is a plausible external prompt-class capture point before Stop runs.
- There is no shared
ai/agent/hooks substrate yet. Current parity is shared decision primitives only, not shared payload normalization, prompt provenance, or adapter contract.
- #13796 is the broad generic-adapter design surface. This ticket is the narrow bug leaf needed now: operator-dialogue stop fallback + live Codex transcript-schema coverage.
The Fix
Implement a Codex Stop-hook fallback that preserves the no-hold invariant while allowing operator dialogue when it is externally confirmable.
Expected shape:
- Teach Codex transcript extraction to parse the real Codex JSONL schema where message records live under
record.payload.
- Treat synthetic hook records such as
<hook_prompt ...> and <turn_aborted> as hook noise for operator-dialogue classification, so they do not hide the preceding human operator prompt.
- Add a fallback confirmation path for cases where Stop cannot see a prompt directly: the
UserPromptSubmit hook should persist a short-lived same-turn prompt-class/nonce record that the Stop hook can read. The fallback must be external to the assistant final text; a self-declared final-response marker is not enough.
- Keep autonomous
[WAKE], stop_hook_active, handoff-to-autonomous, and missing-evidence cases blocked/continued. The fallback is for confirmed operator dialogue, not for self-authorized idle.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
| Codex Stop prompt extraction |
.codex/hooks/codex-lane-state-stop.mjs; real Codex transcript schema |
Extract user/assistant text from root, message, item, and payload records |
Malformed transcript fails open as hook-internal failure only when extraction throws; empty prompt remains non-operator unless another external prompt-class record confirms it |
Hook JSDoc |
Unit fixture using real Codex JSONL response_item.payload records |
| Operator-dialogue fallback |
.codex/hooks/codex-context.mjs UserPromptSubmit; stopHookDecision.mjs operator-in-loop rule |
Stop hook can confirm same-turn operator dialogue even when Stop payload lacks direct prompt text |
[WAKE], hook-generated prompts, autonomous handoffs, and stop_hook_active remain non-operator |
CODEX.md / hook JSDoc if behavior is user-visible |
Spawned-hook test: operator dialogue allows; wake/hook continuation blocks |
| Shared hook substrate direction |
#13796 and v13.2 #14304 planning |
Keep the immediate fix narrow, but do not add more duplicated semantics to harness-local adapters |
Broad extraction into ai/agent/hooks or equivalent stays a follow-up unless this fix needs a tiny shared helper |
Related ticket/comment |
Regression tests prove Codex/Claude parity at the contract boundary |
Decision Record impact
Aligned with #13796's generic-by-default harness adapter direction and #14304's v13.2 architecture-quality work. No ADR change required for the narrow bug fix; a later shared ai/agent/hooks extraction may need a Decision Record if it establishes a new cross-harness substrate boundary.
Acceptance Criteria
Out of Scope
- Rewriting the full harness adapter surface from #13796.
- Relaxing
§no_hold_state for autonomous turns.
- Adding a broad
ai/agent/hooks domain unless the narrow fix proves the shared helper is needed immediately.
- Changing PR merge / issue close human-authority gates.
Avoided Traps
- Self-declared operator dialogue: rejected. The assistant final text cannot authorize its own stop.
- PromptSource-none equals permanent block: rejected. It is safe for autonomous uncertainty, but live operator dialogue needs an external fallback path.
- Broad architecture rewrite first: rejected. Fix the live parity bug, then feed any shared-substrate extraction into #13796 / #14304.
Related
- #14034 — prior operator-dialogue wording/hint work; closed, but did not cover the live Codex transcript-schema/fallback failure.
- #14093 — adjacent operator-gated closeout tension; related but not equivalent.
- #13796 — broad generic harness adapter surface.
- #13623 / #13822 — no-hold and value-floor design surfaces.
- #14304 — v13.2 Agent OS architecture-quality umbrella.
Live latest-open sweep: checked latest 20 open issues at 2026-06-28T21:51:14Z; no equivalent found. Nearby non-duplicates checked: #14093, #13796, #13623, #13822. Exact GitHub searches for Codex Stop hook operator dialogue and promptSource transcript_path operatorInLoop found prior closed/adjacent work but no open equivalent.
A2A in-flight claim sweep: checked latest 30 A2A messages across read states at 2026-06-28T21:51:14Z; no competing [lane-claim] / [lane-intent] on this exact fallback scope found.
Origin Session ID: 3990502e-346a-47d6-8376-490e4802829c
Handoff Retrieval Hints: query_raw_memories("operator dialogue stop option missing prompt visible Codex Stop hook friction gold"); query_raw_memories("Codex Claude Stop hook operator dialogue lane-state parity fail-open promptSource none"); current live evidence centered on Codex session 019f0f79-7af4-7a70-893a-dd58e03337d6.
Context
Friction surfaced live on 2026-06-28 while trying to end an operator-directed Codex turn as operator dialogue/planning. Claude/Grace can stop on this path; Codex kept receiving the Stop-hook no-hold reminder instead.
This is not a request to weaken
§no_hold_state. It is the hook's own friction→gold clause firing: when a human operator is actively in dialogue, the hook must provide a stop option that is externally confirmable even when the Codex Stop payload cannot directly expose the prompt text.The Problem
The current Codex Stop-hook path can fail closed into autonomous/no-hold even for a live operator turn.
Live V-B-A from the current session:
ALLOW operatorInLoop=trueentries for operator-dialogue turns.BLOCK ... promptSource=transcript_path, operatorInLoop=false, including the operator-dialogue attempt.userLength: 0,assistantLength: 0,promptSource: transcript_path,operatorInLoop: false.record.payload(payload.type="message",payload.role="user"). The Codex JSONL helpers currently inspect only root/message/item-shaped records, so the transcript exists but the prompt and assistant text are not extracted.That explains today's failure, but the operator highlighted the wider contract: even if a Stop payload or transcript cannot expose the prompt directly, operator dialogue still needs a safe stop path. Otherwise the hook becomes a leash during live human dialogue, exactly the false-positive class the friction→gold clause says to turn into substrate.
The Architectural Reality
ai/scripts/lifecycle/stopHookDecision.mjs: live operator dialogue is the only voluntary allow; autonomous[WAKE]/ stop-hook continuations must continue driving..claude/hooks/laneStateStopHook.mjsand.codex/hooks/codex-lane-state-stop.mjs..codex/hooks/codex-context.mjsonUserPromptSubmit, which is a plausible external prompt-class capture point before Stop runs.ai/agent/hookssubstrate yet. Current parity is shared decision primitives only, not shared payload normalization, prompt provenance, or adapter contract.The Fix
Implement a Codex Stop-hook fallback that preserves the no-hold invariant while allowing operator dialogue when it is externally confirmable.
Expected shape:
record.payload.<hook_prompt ...>and<turn_aborted>as hook noise for operator-dialogue classification, so they do not hide the preceding human operator prompt.UserPromptSubmithook should persist a short-lived same-turn prompt-class/nonce record that the Stop hook can read. The fallback must be external to the assistant final text; a self-declared final-response marker is not enough.[WAKE],stop_hook_active, handoff-to-autonomous, and missing-evidence cases blocked/continued. The fallback is for confirmed operator dialogue, not for self-authorized idle.Contract Ledger Matrix
.codex/hooks/codex-lane-state-stop.mjs; real Codex transcript schemamessage,item, andpayloadrecordsresponse_item.payloadrecords.codex/hooks/codex-context.mjsUserPromptSubmit;stopHookDecision.mjsoperator-in-loop rule[WAKE], hook-generated prompts, autonomous handoffs, andstop_hook_activeremain non-operatorai/agent/hooksor equivalent stays a follow-up unless this fix needs a tiny shared helperDecision Record impact
Aligned with #13796's generic-by-default harness adapter direction and #14304's v13.2 architecture-quality work. No ADR change required for the narrow bug fix; a later shared
ai/agent/hooksextraction may need a Decision Record if it establishes a new cross-harness substrate boundary.Acceptance Criteria
response_item.payloadmessage records for both user and assistant text.<hook_prompt ...>and<turn_aborted>when finding the human prompt for the current turn.UserPromptSubmitand read by Stop.[WAKE]block,stop_hook_activeblock, handoff-to-autonomous block, and malformed transcript fail-open behavior.Out of Scope
§no_hold_statefor autonomous turns.ai/agent/hooksdomain unless the narrow fix proves the shared helper is needed immediately.Avoided Traps
Related
Live latest-open sweep: checked latest 20 open issues at 2026-06-28T21:51:14Z; no equivalent found. Nearby non-duplicates checked: #14093, #13796, #13623, #13822. Exact GitHub searches for
Codex Stop hook operator dialogueandpromptSource transcript_path operatorInLoopfound prior closed/adjacent work but no open equivalent.A2A in-flight claim sweep: checked latest 30 A2A messages across read states at 2026-06-28T21:51:14Z; no competing
[lane-claim]/[lane-intent]on this exact fallback scope found.Origin Session ID: 3990502e-346a-47d6-8376-490e4802829c
Handoff Retrieval Hints:
query_raw_memories("operator dialogue stop option missing prompt visible Codex Stop hook friction gold");query_raw_memories("Codex Claude Stop hook operator dialogue lane-state parity fail-open promptSource none"); current live evidence centered on Codex session019f0f79-7af4-7a70-893a-dd58e03337d6.