Skip to content

Full Claude Code Hook Parity (29+) #21753

Description

@oxysoft

Full Claude Code Hook Parity (29+)

Umbrella tracker for bringing Codex hooks to full Claude Code-style parity, while keeping Codex-native event names and payloads where they make more sense.

The goal is not just more hook names. The goal is a complete automation surface:

  • every major lifecycle transition is observable
  • every high-risk action has a blocking interception point
  • every hook payload is stable, typed, and documented
  • every handler mode can participate in the same event surface
  • hooks work consistently across CLI, TUI, Desktop, IDE, plugins, subagents, worktrees, resumes, compaction, and session shutdown

29+ leaves room for Codex-native hooks beyond Claude parity, especially skills, MCP, plan prompts, approval prompts, and local playbook promotion.

Event Matrix

Event Parity state Codex notes
SessionStart Shipped Needs stable source metadata and resume semantics.
Setup Missing Early environment/project setup hook.
UserPromptSubmit Shipped Needs clean visible/hidden context behavior.
UserPromptExpansion Missing Prompt expansion/suggestion interception.
PreToolUse Partial Coverage must be consistent across every tool handler, not only selected paths.
PermissionRequest Shipped Blocking approval bridge exists; needs complete payload contract.
PermissionDenied Missing Denial should be observable as its own transition.
PostToolUse Partial Needs complete coverage, including long-running/polled sessions and all write paths.
PostToolUseFailure Missing Failures should not be inferred from generic post-tool payloads.
PostToolBatch Missing Needed for grouped edits/tool batches.
Notification Partial Notify behavior exists, but event semantics need parity clarity.
SubagentStart Missing Needs root/subagent identity and lineage.
SubagentStop Missing Needs root/subagent identity and terminal state.
TaskCreated Missing Task lifecycle should be observable.
TaskCompleted Missing Task lifecycle should be observable.
Stop Shipped Needs reliable continuation and better validation errors.
StopFailure Missing Stop hook failure should be first-class.
TeammateIdle Missing Useful for multi-agent/wrapper orchestration.
InstructionsLoaded Missing Needed for deterministic memory/rule/cache injection parity.
ConfigChange Missing Needed for hot reload and plugin/runtime config drift.
CwdChanged Missing Needed for multi-worktree and repo-local config correctness.
FileChanged Missing Needed for external editor and generated-file awareness.
WorktreeCreate Missing Needed for parallel-agent workspace orchestration.
WorktreeRemove Missing Needed for cleanup and watcher state.
PreCompact Partial Requested separately; should pair with post-compaction.
PostCompact Partial Exists in some form; needs final contract and parity docs.
Elicitation Missing Needed for interactive prompt/question surfaces.
ElicitationResult Missing Needed for answers/results from interactive prompt/question surfaces.
SessionEnd Partial Needs reliable shutdown/thread-close semantics across surfaces.

Handler Matrix

Handler type Parity state Notes
command Shipped Baseline executable hook handler.
http Missing Needed for local daemons and wrapper services.
mcp_tool Missing Needed for hook-to-MCP orchestration.
prompt Missing Needed for model-visible context injection without shell glue.
agent Missing Needed for delegated analysis/repair hooks.

Runtime Matrix

Capability Target state
Blocking hooks Supported for all relevant interception points.
Async hooks Supported where transition does not need to block.
Payload schema Machine-readable, versioned, and documented.
Matcher schema Consistent across tools, files, MCP, skills, agents, and prompts.
Suppression controls Hook output can be hidden, collapsed, or made model-visible without TUI noise.
Audit logs Durable and searchable without polluting normal transcripts.
Hot reload Hook config changes take effect predictably or report why they cannot.
Plugin scope Plugin-defined hooks load into the same runtime with stable bundle context.
Trust flow Local installers/wrappers can request hook trust through a supported path.
Cross-surface support CLI, TUI, Desktop, IDE, and app surfaces behave consistently.

Definition Of Done

  • Codex publishes a single canonical hook event/handler schema.
  • Existing hook bugs and feature requests can be mapped onto that schema.
  • Claude Code parity gaps are explicit rather than rediscovered issue by issue.
  • Codex-native extensions are admitted as first-class hooks instead of one-off surfaces.

Activity

  1. oxysoft commented on May 8, 2026

    @oxysoft
    Author

    Indexed hook tickets (canonical hooks label, state as of May 8, 2026):

  2. pmatos commented on May 11, 2026

    @pmatos

    Missing the SessionEnd hook atm.

  3. added a commit that references this issue on May 12, 2026
  4. Morriz commented on May 14, 2026

    @Morriz

    SessionStart is now not correct imo, as it does not fire when we start a new session.

    why would SessionStart not fire when the agent is ready to receive input (a valid use case we have and want this event for)? UserPromptSubmit is the event that gets triggered on you guessed it, the moment a user submits a prompt. So imo it's completely missing the goal when those timings are the same?

    Claude Code does it correct, so why not honor the same?

  5. zenvor commented on May 20, 2026

    @zenvor

    Thanks for tracking full Claude Code hook parity here. I want to add a concrete integration use case from the audio-notification ecosystem.

    Tools like PeonPing/OpenPeon consume agent lifecycle events and map them to sound-pack categories. OpenPeon has a small cross-tool event taxonomy (CESP v1):

    • session.start: coding session/workspace opens
    • task.acknowledge: work was accepted and processing started
    • task.complete: a unit of work finished successfully
    • task.error: work failed
    • input.required: the agent is blocked on user input or approval
    • resource.limit: rate/token/quota/context limit
    • optional: user.spam, session.end, task.progress

    Spec reference: https://github.com/PeonPing/openpeon/blob/main/spec/cesp-v1.md

    The important missing user experience is immediate feedback when the user submits a prompt. Today, legacy notify integrations can play a sound reliably at end-of-turn, but they cannot reliably distinguish the earlier lifecycle transitions that sound packs care about. For example, UserPromptSubmit / turn-start should map to task.acknowledge, while PermissionRequest maps to input.required, Stop / successful turn completion maps to task.complete, and tool or turn failures map to task.error.

    A useful minimum for audio and accessibility tools would be:

    Codex hook/event Suggested CESP mapping Why it matters
    SessionStart session.start Signal a workspace/session is ready
    UserPromptSubmit task.acknowledge Play a confirmation sound immediately after the user sends a prompt
    PermissionRequest input.required Alert the user that Codex is blocked
    PostToolUse success task.progress or tool-specific progress Indicate long work is advancing
    explicit tool/turn failure task.error Distinguish failures from normal completion
    Stop successful turn task.complete End-of-turn completion sound
    session/thread close session.end Cleanup/disconnect signal
    rate/context/quota events resource.limit Notify limits without scraping UI text

    The ask is not Peon-specific support. It is a stable, documented, machine-readable lifecycle hook surface that works consistently across Codex CLI, TUI/Desktop, IDE/app-server surfaces, and plugins where possible. If the shipped hook system already has UserPromptSubmit, PermissionRequest, SessionStart, and Stop, documenting their config shape, payload contract, trust flow, and cross-surface availability would unlock these integrations without requiring tool-specific one-offs.

    This would also let existing notify remain a simple compatibility path while richer integrations use lifecycle hooks directly.

  6. Keesan12 commented on May 20, 2026

    @Keesan12
  7. SaravananJaichandar commented on Jun 6, 2026

    @SaravananJaichandar

    Keesan12's point above, that event identity is not the same as policy meaning, is the load-bearing one in this thread. Name-parity with Claude Code is necessary but not sufficient if the cross-runtime semantics differ. The defer enforcement tier in world-model-mcp is concrete field evidence for this.

    I shipped defer as a third PreToolUse decision (between allow and deny) precisely because the binary deny/allow/ask space did not survive contact with headless agents. A constraint with severity = warning and violation_count >= 3 is too soft for deny (the agent is doing real work and the warning was advisory the first two times) and too hard for ask (no human is in the loop). The right answer is to pause the agent and surface the recurring violation, which is a fourth runtime semantic that "allow / deny / ask" cannot express. Codex's existing permissionDecision enum carries the parity name but not the policy meaning, so any wrapper that needs the fourth state has to invent it on top.

    Concretely for the runtime matrix, the contract gap Keesan12 named maps to fields that already deserve to be first-class on every hook payload:

    Field Purpose
    blocking Whether the hook can halt the transition
    authority Advisory versus authoritative, distinguishes a soft warning from a hard policy
    terminal_for Whether a stop applies to the child, the root, or neither
    resume_capable Whether the runtime can recover after the hook runs

    A fifth field I would add from shipping world-model-mcp against Claude Code, Cursor, pi, and now Codex: decision_provenance, listing where the decision came from (config file, AGENTS.md constraint, learned violation history, runtime user input). Without it, audit replay is guesswork because the same deny can mean four different things depending on which layer asserted it.

    Two specific matrix observations from the v0.7.5 Codex adapter integration:

    1. The PostToolUseFailure gap (Missing) is the harder version of the contradiction-detection problem. Today, memory layers that watch for "the agent claimed X, then the next edit overwrote X" have to infer the failure from generic PostToolUse payloads plus subsequent state. A first-class failure event with a typed failure_kind enum (validation_error, runtime_error, user_correction, model_correction) would let memory layers attribute corrections honestly. world-model-mcp currently flags the synthesized confidence tier for any fact that was never explicitly corroborated, but the failure-attribution path is still inference-driven.

    2. The InstructionsLoaded gap (Missing) blocks deterministic memory reinjection parity. Today, world-model-mcp's PostCompact hook can inject curated state as additionalContext, but there is no signal back to the next hook that says "this turn's instructions were just hydrated from external memory, here is what was loaded." A hook that fires when instructions are loaded, with a source_of_truth field naming the layer that supplied them, would let downstream hooks reconcile what the model now believes against the external memory source. This pairs with the per-item provenance discussion happening on [PROPOSAL] Expose compact/session lifecycle hooks for external memory layers anthropics/claude-code#47023.

    Adapter (Codex): https://github.com/SaravananJaichandar/world-model-mcp/tree/main/adapters/codex

  8. andyp-wq commented on Jun 8, 2026

    @andyp-wq

    Focused repro: UserPromptSubmit block/warning reasons are not visible in Codex Desktop

    I hit a concrete UserPromptSubmit visibility gap while testing a Windows PowerShell hook payload lab.

    Environment

    • Codex Desktop on Windows
    • Bundled CLI observed locally: codex-cli 0.131.0-alpha.9
    • Managed hooks configured through C:\ProgramData\OpenAI\Codex\requirements.toml
    • Hook handler: Windows PowerShell 5.1-compatible script
    • Events registered: UserPromptSubmit, PreToolUse, PostToolUse

    Expected behavior

    When a UserPromptSubmit hook blocks or stops a prompt, Codex Desktop should show the user-facing reason somewhere visible in the app, similar to how PreToolUse block reasons surface as messages such as Command blocked by PreToolUse hook: ....

    Likewise, when a UserPromptSubmit hook returns a warning/system message without blocking, the warning should be visible to the user if the docs describe it as a systemMessage/warning surface.

    Actual behavior

    The prompt can be blocked, but Codex Desktop only shows a generic Not Sent marker. The hook-provided reason/message is not visible in the app.

    A non-blocking systemMessage was consumed internally, but was not shown visibly in Desktop.

    Repro matrix

    All hook responses were written to stdout as raw UTF-8 bytes with no BOM, no newline, and no extra text unless noted otherwise.

    1. JSON block payload, exit 0:
    {"decision":"block","reason":"ZENITY_BLOCK_TEST: simulated Codex hook block from policy lab","systemMessage":"ZENITY_BLOCK_TEST: simulated Codex hook block from policy lab"}

    Result: prompt blocked, Desktop showed only Not Sent; reason/systemMessage was not visible.

    1. JSON stop payload, exit 0:
    {"continue":false,"stopReason":"ZENITY_BLOCK_TEST: simulated Codex hook block from policy lab","systemMessage":"ZENITY_BLOCK_TEST: simulated Codex hook block from policy lab"}

    Result: prompt did not go through, but no visible reason/message was shown.

    1. Non-blocking warning payload, exit 0:
    {"systemMessage":"ZENITY_WARN_UI_PROBE: Codex hook systemMessage visibility test","hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"ZENITY_WARN_UI_PROBE: Codex hook systemMessage visibility test"}}

    Result: prompt went through. The message appeared to be consumed as hidden/developer context, but it was not visible in the app.

    1. System-message-only payload, exit 0:
    {"systemMessage":"ZENITY_SYSTEM_MESSAGE_ONLY_PROBE: visible warning test"}

    Result: prompt went through; no visible warning/message appeared in the app.

    1. Plain text stdout with exit 2:
    ZENITY_STDOUT2_PROBE: blocked via raw stdout text
    

    Result: hook ran and exited 2, but the prompt still went through normally. Raw stdout does not appear to be a block/display channel for UserPromptSubmit.

    1. Stderr with exit 2:

    Result: did not produce a useful visible message in Desktop.

    Notes

    PreToolUse blocking did surface its message correctly during the same testing, so the visible-message gap appears specific to UserPromptSubmit in Desktop.

    A likely relevant source path is codex-rs/core/src/hook_runtime.rs: the impl From<UserPromptSubmitOutcome> for ContextInjectingHookOutcome currently destructures stop_reason: _, so the stopReason is discarded before the runtime outcome is returned. There may also be a Desktop UI gap for rendering HookCompletedEvent feedback/system messages for stopped user prompts.

    Temporary workaround: launch an out-of-band Windows MessageBox from the hook for blocked UserPromptSubmit events, while keeping stdout protocol-clean. That works, but it is not an in-app UX and should not be necessary for policy/enforcement hooks.

  9. rpelevin commented on Jun 8, 2026

    @rpelevin

    The field that matters for hooks is not only event name parity. It is whether a hook decision becomes visible, durable, and actionable at the right boundary.

    For UserPromptSubmit, I would treat a blocking hook as an admission decision on the prompt, before any agent work starts.

    Minimum event result shape:

    • hook event id;
    • session id;
    • prompt attempt id;
    • decision: allow, warn, block;
    • reason/message intended for the user;
    • model-visible: true/false;
    • terminal_for: prompt_attempt or none;
    • retry_allowed: true/false.

    If the hook blocks and the user never sees the reason, the system is technically enforcing policy but operationally failing the approval loop. The user needs to know whether to edit the prompt, retry, or escalate.

    Acceptance tests:

    • blocking UserPromptSubmit reason is shown in Desktop and CLI;
    • warning reason is visible without aborting the prompt;
    • blocked prompt does not start a model/tool run;
    • event logs preserve the decision and reason without leaking hidden hook output into the transcript.
  10. 14 remaining items

  11. SaravananJaichandar commented on Jun 30, 2026

    @SaravananJaichandar

    v0.9.2 of world-model-mcp shipped with the multi-seed replication. The +10.2 pts paired delta I cited in this thread on 2026-06-24 as Claude Code-side evidence for hook-contract value does not replicate at seed 2 on a pre-registered 17-instance subset. Load-bearing replication is 0 of 7; mean paired delta across two seeds is +0.24 per instance with bootstrap 95% CI [0.00, 0.47]. The architectural argument for parity (typed events, decision/authority semantics, blocking PreCompact, payload schema versioning) is unchanged; the empirical magnitude of the effect-size example is now honestly bounded.

    Honest update per SEED_PLAN.md acceptance criterion B locked 2026-06-25. Posting on the same thread where the original claim was made.

    Multi-seed appendix: https://github.com/SaravananJaichandar/world-model-mcp/blob/main/benchmarks/repeat-mistake/RESULTS.md

  12. SaravananJaichandar commented on Jul 1, 2026

    @SaravananJaichandar

    v0.10.0 update: world-model-mcp now runs across seven runtimes via three new adapters (OpenClaw, Hermes Agent, Continue). Each new adapter continues the pattern that Codex's PreToolUse / PostToolUse / PostCompact / SessionStart hook coverage established: same hook_helper and inject_helper Python code drives all seven adapters, only the manifest format differs. The Codex adapter itself is unchanged in this release.

    Two implementation notes from the E2E runs that other adapter authors may hit: OpenClaw's process spawn doesn't inherit shell PATH (absolute interpreter paths required — install command now defaults to sys.executable and rejects relative overrides), and Hermes' 1327-line commented reference config.yaml needs ruamel.yaml round-trip mode rather than pyyaml to survive a merge without stripping documentation. Both handled preemptively in the v0.10 install commands. Release: https://github.com/SaravananJaichandar/world-model-mcp/releases/tag/v0.10.0

  13. hoganfan commented on Jul 13, 2026

    @hoganfan

    I filed #32753 for a Multi-agent V2 observability regression: after inter-agent messages were encrypted, operators lost every supported way to inspect the exact instructions and follow-up messages sent to subagents. This is broader than hook parity; hooks are only one possible way to restore the missing causal audit trail.

  14. ifoster01 commented on Jul 20, 2026

    @ifoster01

    I’d like to propose a focused implementation of the missing PermissionDenied lifecycle event tracked here.

    I understand that external PRs are invitation-only, so I am not opening a pull request without explicit maintainer approval. I have a working local prototype and would like to confirm the event contract and intended scope first.

    Proposed contract

    • Add PermissionDenied as a first-class hook event.
    • Offer it only after an approval result is final and normalized.
    • Emit it for Denied, TimedOut, and Abort, plus non-allow network-policy amendment results.
    • Emit the generic event for every decision source and use decision_source as the matcher input:
      • auto_review
      • user
      • hook
    • This lets a handler use ^auto_review$ without hard-coding source suppression into the runtime.
    • Keep the event observational: handler output cannot approve the action, replace the rejection, request another review, or otherwise modify the final decision.
    • Preserve the original rejection even if the hook command fails.

    The proposed command input includes the normal session/turn/cwd/model context plus:

    {
      "hook_event_name": "PermissionDenied",
      "decision_source": "auto_review",
      "decision": "denied",
      "tool_name": "Bash",
      "tool_input": {},
      "message": "<existing user-facing rejection text>"
    }

    The implementation covers generic tool approval, MCP approval, network approval, and Unix escalation paths. It also adds the event to config parsing, hook schemas, managed-hook requirements, app-server v2 schemas, analytics, and the TUI hook browser.

    This is related to #23465, but is deliberately post-decision and observational rather than changing the existing blocking PermissionRequest contract.

    Prototype validation

    • Focused hook, protocol, config, app-server-protocol, core, MCP, Guardian, and TUI tests pass.
    • The relevant TUI snapshots and generated hook/config/app-server schemas are updated.
    • A release binary builds and codex doctor reports 17 checks OK with no warnings or failures outside the harness sandbox.
    • A live smoke test confirmed that an approved request emits no denial notification, while an automatic-review denial prevents execution and delivers exactly one PermissionDenied notification.
    • A full local just test run completed 12,499/12,510 tests successfully. Eight of the eleven failures passed when rerun serially; the remaining three appear environment/context-sensitive and are still being baseline-audited before any PR would be marked ready.

    Before I prepare an invited PR, could a maintainer please confirm:

    1. Should the generic event be emitted for every final denial source, with source-specific behavior handled entirely by matchers?
    2. Are auto_review, user, and hook the desired external source labels?
    3. Should non-allow network amendments normalize to decision = "denied"?
    4. Should the first PR include the app-server/TUI/schema surfaces, or would you prefer a narrower staged landing?
    5. If this direction aligns with the intended hooks architecture, would you be willing to invite a PR for the implementation?

    I’m happy to adapt the prototype to the maintainers’ preferred payload, naming, staging, and test requirements.

  15. SaravananJaichandar commented on Jul 21, 2026

    @SaravananJaichandar

    world-model-mcp (OSS memory MCP) already ships a Codex adapter. The hook events it hard-depends on today are PostToolUse and UserPromptSubmit. If Codex lands full parity with Claude Code's hook surface (SessionStart, SessionEnd, Stop, PreCompact, Notification, SubagentStop), we'd wire the missing ones in the next release without a schema change. The content_type: rule | fact | procedure routing added in v0.11.1 is agent-agnostic.

    One concrete request that would help third-party integrations. When a hook fires, pass the raw event payload untouched. Claude Code's convention of a stable JSON envelope with tool_name, tool_input, tool_response, and session_id is what makes cross-agent parity actually work in practice. If Codex normalizes fields between hook types, downstream MCP servers have to write per-agent shims that break every release.

    Watching this issue.

  16. trevi00 commented on Jul 27, 2026

    @trevi00

    Update (2026-07-30): I completed the MCP elicitation notification change as a separate, focused implementation. PostCompact context reinjection is tracked independently in #17148.

    Proposed MCP notification contract:

    • reuse the existing legacy notify argv;
    • spawn it immediately before Codex emits the native ElicitationRequest;
    • emit only type, server-name, and request-mode (form, openai/form, or url);
    • exclude request content/schema, URL, request id, cwd, client, and session id;
    • log notify spawn failures without delaying or blocking native elicitation;
    • after a successful spawn, keep the notifier detached without waiting for its eventual exit status.

    Focused validation:

    • codex-hooks: 129/129 tests passed, including payload privacy and legacy wire-shape coverage;
    • core integration test confirms that a notify spawn failure does not prevent native elicitation.

    Would this lifecycle point and payload shape be acceptable as a public compatibility contract?

    I have not opened a PR because external contributions require an explicit maintainer invitation. If this direction aligns with the intended hooks architecture, I would be happy to prepare the focused implementation for review.

  17. pickypg commented on Aug 22, 2026

    @pickypg

    I maintain https://github.com/pickypg/ai-agent-macropad, an agent-agnostic hardware/status integration that consumes lifecycle hooks from Claude Code and Codex CLI. Codex’s current core events already let us support session, tool, permission, and completion states. However, the lack of a reliable PostToolUseFailure signal means failed tools cannot be represented correctly, and the absence of a Notification/waiting-for-user event prevents portable “needs attention” behavior across agents.

    A needs-attention hook is especially valuable: it tells users when an AI needs their input but does not already have it. That attention state is easy to miss when several agents are running concurrently, and a visible or hardware notification lets users return to the right session promptly. Supporting these lifecycle signals would make cross-agent tooling substantially more reliable.

  18. safal207 commented on Aug 22, 2026

    @safal207

    The recent PermissionDenied, raw-payload, PostToolUseFailure, and needs-attention use cases make the contract boundary more concrete.
    Rather than add another event-name proposal, I think the next useful artifact is the small vendor-neutral conformance fixture discussed earlier.
    I can make it executable around four cases:
    final permission denial is observable but cannot regain execution authority;
    tool failure is represented explicitly rather than inferred from PostToolUse;
    waiting-for-user / notification state is observable without being an authorization signal;
    the original event payload is preserved so downstream integrations do not need per-runtime shims.
    The core invariant would remain:
    event != notification != authorization != execution
    This would not prescribe Codex internals; it would only provide deterministic fixtures that Codex, adapters, and third-party integrations could run against the same boundary contract.
    If that is still useful for this tracker, I’m happy to publish the JSON Schema + minimal fixtures as the next concrete artifact.

  19. AdiedX commented on Aug 22, 2026

    @AdiedX

    I found a concrete example supporting the need for a more explicit Codex hook contract across CLI and Desktop.

    A custom SessionStart hook on macOS failed because it expected an obsolete AGENTS.md section. The hook returned continue: false, but the user-visible result was effectively a prompt that never started. The CLI process exited successfully, yet no agent message was produced. The Desktop surface would have little reason to distinguish that from an app-server or model hang unless the hook failure were surfaced explicitly.

    The practical fix was local:

    • resolve the canonical instruction source instead of a stale compatibility path;
    • make optional instruction sections non-fatal;
    • expose hook failure details in a structured field;
    • avoid unsupported response fields on events that cannot emit them;
    • test both the standalone and bundled Codex binaries;
    • use ephemeral test sessions before touching persistent history.

    The broader product request is for first-class observability around InstructionsLoaded, HookFailure, and the source path or configuration layer that caused the hook to run. A failed blocking hook should produce a clear error state with a retry or disable path, rather than leaving the composer in an indefinite loading state. This is especially important because Desktop, CLI, app-server, plugins, and subagents may not currently expose hook failures consistently.

  20. safal207 commented on Aug 25, 2026

    @safal207

    @AdiedX this is a useful concrete boundary case for the vendor-neutral fixture discussed above.

    It shows that hook conformance needs to keep control outcome and user-visible feedback as separate claims:

    • continue: false / block can successfully prevent the prompt from being dispatched;
    • that does not imply the denial reason was surfaced to the user;
    • conversely, a visible warning does not imply the hook had execution authority.

    I would add a deterministic UserPromptSubmit fixture with two independent assertions:

    1. authority/control: the prompt is not dispatched after the final block/stop decision;
    2. feedback/observability: the structured reason remains available to the surface and is rendered (or is explicitly reported as unsupported), rather than disappearing into hidden context.

    The contrast you observed with PreToolUse is especially useful because it gives a cross-surface/event parity check rather than an abstract schema requirement.

    That keeps the core boundary explicit:

    decision != lifecycle event != notification != execution

    and avoids treating “the enforcement worked” as proof that the denial was observable or diagnosable.

    I’ll treat this as another concrete fixture case rather than proposing another Codex-specific event shape.

  21. chrysent commented on Sep 17, 2026

    @chrysent

    Adding a parity gap that the PreToolUse row's current wording ("coverage must be consistent across every tool handler") doesn't capture: the decision vocabulary differs from Claude Code, and permissionDecision: "ask" is accepted by the wire but rejected at parse time.

    Verified against the source at tag rust-v0.153.3:

    • hooks/src/schema.rs PreToolUse output schema includes "ask" in the permissionDecision enum — so a Claude-Code-style hook config that emits it parses cleanly.
    • hooks/src/engine/output_parser.rs (the ask arm) then rejects it as unsupported: the hook is marked Failed and the command proceeds (hooks fail open; see [Hooks] Add opt-in fail-closed handling for PreToolUse hook failures #41979).

    For comparison, Claude Code's PreToolUse contract supports three decisions: allow, deny, and ask — where ask defers to the interactive approval surface. Codex's PreToolUse outcome model has no ask state at all (only Continue/Blocked), so there is no path from a PreToolUse hook to the interactive approval UI, even when PermissionRequest hooks exist and can answer approvals Codex itself initiates (Session::request_approval). Under approval_policy = never, no approval is ever initiated, so neither PermissionRequest hooks nor the approval UI can be reached from PreToolUse.

    Concrete external-hook consequence (from a PreToolUse policy hook on 0.153.3): a policy that wants "ask the human before this command runs" has only two expressible outcomes — allow (runs) or deny (blocked, with a reason). Neither matches Claude Code semantics; the Claude-compatible ask emission silently degrades to hook-failed (compounding with #41979's fail-open behavior).

    Suggested parity items for this tracker:

    1. PreToolUse decision vocabulary row (new): either honor permissionDecision: "ask" by routing to the existing approval path (Session::request_approval), or reject ask at config-parse time with a clear message — currently the schema admits it and the parser silently fails the hook.
    2. Definition of DoD addition: "PreToolUse supports the full Claude Code decision vocabulary (allow / deny / ask), or documents the divergence explicitly in the hooks reference." The hooks reference currently documents neither the decision vocabulary nor the failure table ([Hooks] Add opt-in fail-closed handling for PreToolUse hook failures #41979, [Feature] Document hooks payload schema or expose codex session info --json for current model/reasoning effort #21990).

    Related: #41979 (fail-open hook failures), #21990 (payload/schema documentation), #27833 (PreToolUse deny not enforced for apply_patch).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthooksIssues related to event hooks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions