Full Claude Code Hook Parity (29+)
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.
28 Comments
Indexed hook tickets (canonical
hookslabel, state as of May 8, 2026):https://github.com/openai/codex/issues/2109
https://github.com/openai/codex/issues/8929
https://github.com/openai/codex/issues/11912
https://github.com/openai/codex/issues/12341
https://github.com/openai/codex/issues/14754
https://github.com/openai/codex/issues/14814
https://github.com/openai/codex/issues/14882
https://github.com/openai/codex/issues/15266
https://github.com/openai/codex/issues/15269
https://github.com/openai/codex/issues/15311
https://github.com/openai/codex/issues/15486
https://github.com/openai/codex/issues/15490
https://github.com/openai/codex/issues/15497
https://github.com/openai/codex/issues/16098
https://github.com/openai/codex/issues/16226
https://github.com/openai/codex/issues/16246
https://github.com/openai/codex/issues/16301
https://github.com/openai/codex/issues/16466
https://github.com/openai/codex/issues/16484
UserPromptSubmit.additionalContextas multiline hook context in the TUIhttps://github.com/openai/codex/issues/16486
https://github.com/openai/codex/issues/16732
https://github.com/openai/codex/issues/16743
https://github.com/openai/codex/issues/16933
https://github.com/openai/codex/issues/16993
https://github.com/openai/codex/issues/17105
https://github.com/openai/codex/issues/17132
https://github.com/openai/codex/issues/17148
https://github.com/openai/codex/issues/17321
hooks, but plugin hooks are not loaded into the Codex hooks runtimehttps://github.com/openai/codex/issues/17331
https://github.com/openai/codex/issues/17333
https://github.com/openai/codex/issues/17341
https://github.com/openai/codex/issues/17421
https://github.com/openai/codex/issues/17422
https://github.com/openai/codex/issues/17478
https://github.com/openai/codex/issues/17479
https://github.com/openai/codex/issues/17518
https://github.com/openai/codex/issues/17532
https://github.com/openai/codex/issues/17636
https://github.com/openai/codex/issues/17794
https://github.com/openai/codex/issues/17930
https://github.com/openai/codex/issues/18051
https://github.com/openai/codex/issues/18067
https://github.com/openai/codex/issues/18090
https://github.com/openai/codex/issues/18295
https://github.com/openai/codex/issues/18491
UserPromptSubmithook withoutStophook afterwardshttps://github.com/openai/codex/issues/18541
https://github.com/openai/codex/issues/18607
https://github.com/openai/codex/issues/18887
https://github.com/openai/codex/issues/19061
https://github.com/openai/codex/issues/19199
https://github.com/openai/codex/issues/19328
https://github.com/openai/codex/issues/19364
https://github.com/openai/codex/issues/19383
https://github.com/openai/codex/issues/19385
https://github.com/openai/codex/issues/19666
https://github.com/openai/codex/issues/19780
https://github.com/openai/codex/issues/19921
https://github.com/openai/codex/issues/20204
SessionEndhook event for session/thread shutdownhttps://github.com/openai/codex/issues/20374
https://github.com/openai/codex/issues/20603
https://github.com/openai/codex/issues/20616
https://github.com/openai/codex/issues/20675
https://github.com/openai/codex/issues/20766
https://github.com/openai/codex/issues/20783
hook timed out after 60seven when the hook exits 0 in ~4shttps://github.com/openai/codex/issues/20862
https://github.com/openai/codex/issues/20879
https://github.com/openai/codex/issues/20985
/codex/hooks.returns 404)https://github.com/openai/codex/issues/21148
https://github.com/openai/codex/issues/21160
https://github.com/openai/codex/issues/21615
https://github.com/openai/codex/issues/21639
https://github.com/openai/codex/issues/21675
https://github.com/openai/codex/issues/21696
https://github.com/openai/codex/issues/21723
https://github.com/openai/codex/issues/21748
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Missing the SessionEnd hook atm.
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?
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 openstask.acknowledge: work was accepted and processing startedtask.complete: a unit of work finished successfullytask.error: work failedinput.required: the agent is blocked on user input or approvalresource.limit: rate/token/quota/context limituser.spam,session.end,task.progressSpec 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
notifyintegrations 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 totask.acknowledge, whilePermissionRequestmaps toinput.required,Stop/ successful turn completion maps totask.complete, and tool or turn failures map totask.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 ||
PostToolUsesuccess |task.progressor tool-specific progress | Indicate long work is advancing || explicit tool/turn failure |
task.error| Distinguish failures from normal completion ||
Stopsuccessful 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, andStop, 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
notifyremain a simple compatibility path while richer integrations use lifecycle hooks directly.One cross-runtime seam worth preserving is event identity versus policy meaning.
If Codex chases Claude Code parity by name alone, wrappers still end up branching on runtime-specific semantics because
PermissionRequest,Stop, andNotificationdo not all carry the same authority. A small shared contract on every hook event such asblocking=true|false,authority=advisory|authoritative,terminal_for=child|root|none, andresume_capable=true|falsewould make migration much easier than one-to-one event-count parity.That would let Codex stay native while still being a credible target for Claude Code-oriented automation stacks.
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
deferenforcement tier in world-model-mcp is concrete field evidence for this.I shipped
deferas a third PreToolUse decision (betweenallowanddeny) precisely because the binary deny/allow/ask space did not survive contact with headless agents. A constraint withseverity = warningandviolation_count >= 3is too soft for deny (the agent is doing real work and the warning was advisory the first two times) and too hard forask(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 existingpermissionDecisionenum 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 samedenycan mean four different things depending on which layer asserted it.Two specific matrix observations from the v0.7.5 Codex adapter integration:
PostToolUseFailuregap (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 typedfailure_kindenum (validation_error, runtime_error, user_correction, model_correction) would let memory layers attribute corrections honestly. world-model-mcp currently flags thesynthesizedconfidence tier for any fact that was never explicitly corroborated, but the failure-attribution path is still inference-driven.InstructionsLoadedgap (Missing) blocks deterministic memory reinjection parity. Today, world-model-mcp's PostCompact hook can inject curated state asadditionalContext, 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 asource_of_truthfield 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 anthropics/claude-code#47023.Adapter (Codex): https://github.com/SaravananJaichandar/world-model-mcp/tree/main/adapters/codex
Focused repro:
UserPromptSubmitblock/warning reasons are not visible in Codex DesktopI hit a concrete
UserPromptSubmitvisibility gap while testing a Windows PowerShell hook payload lab.Environment
codex-cli 0.131.0-alpha.9C:\ProgramData\OpenAI\Codex\requirements.tomlUserPromptSubmit,PreToolUse,PostToolUseExpected behavior
When a
UserPromptSubmithook blocks or stops a prompt, Codex Desktop should show the user-facing reason somewhere visible in the app, similar to howPreToolUseblock reasons surface as messages such asCommand blocked by PreToolUse hook: ....Likewise, when a
UserPromptSubmithook returns a warning/system message without blocking, the warning should be visible to the user if the docs describe it as asystemMessage/warning surface.Actual behavior
The prompt can be blocked, but Codex Desktop only shows a generic
Not Sentmarker. The hook-provided reason/message is not visible in the app.A non-blocking
systemMessagewas 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.
Result: prompt blocked, Desktop showed only
Not Sent; reason/systemMessage was not visible.Result: prompt did not go through, but no visible reason/message was shown.
Result: prompt went through. The message appeared to be consumed as hidden/developer context, but it was not visible in the app.
Result: prompt went through; no visible warning/message appeared in the app.
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.Result: did not produce a useful visible message in Desktop.
Notes
PreToolUseblocking did surface its message correctly during the same testing, so the visible-message gap appears specific toUserPromptSubmitin Desktop.A likely relevant source path is
codex-rs/core/src/hook_runtime.rs: theimpl From<UserPromptSubmitOutcome> for ContextInjectingHookOutcomecurrently destructuresstop_reason: _, so thestopReasonis discarded before the runtime outcome is returned. There may also be a Desktop UI gap for renderingHookCompletedEventfeedback/system messages for stopped user prompts.Temporary workaround: launch an out-of-band Windows MessageBox from the hook for blocked
UserPromptSubmitevents, while keeping stdout protocol-clean. That works, but it is not an in-app UX and should not be necessary for policy/enforcement hooks.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:
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:
UserPromptSubmitreason is shown in Desktop and CLI;@rpelevin — agreed on the framing. Visibility-durability-actionability at the right boundary is the same axis Keesan12 named earlier in the thread (event identity versus policy meaning), and your payload sketch composes cleanly with the four fields proposed above (
blocking,authority,terminal_for,resume_capable).Your acceptance tests are the right ones, and @andyp-wq's Desktop repro just above is concrete evidence for why the visibility leg of the contract has to be enforced, not assumed. A blocking
UserPromptSubmitthat surfaces only "Not Sent" is exactly the operational failure mode the acceptance tests would catch.One substantive disagreement on the decision enum:
allow | warn | blockis sufficient for prompt admission, but my v0.7.0 shipping experience on the PreToolUse side argues for a fourth state when the loop is headless. I shippeddeferprecisely becausewarnis too soft (the agent ignores the warning the next time the constraint fires) andblockis too hard (no human is in the loop to unblock). ForUserPromptSubmitspecifically the three-value enum probably holds, since prompt submission is a user-initiated event by definition. But the moment the same contract is generalized across other hook events that fire mid-turn or in headless runs, the fourth state earns its keep.The cleanest reconciliation:
decisioncarries the three-value enum at the event boundary, andauthority(from the Keesan12 fields) carries advisory-versus-authoritative independently. Adecision: warnplusauthority: authoritativeis the defer case;decision: warnplusauthority: advisoryis the soft warning. That keeps your enum clean while preserving the runtime semantic the headless case needs.That reconciliation makes sense to me.
I would keep
decisionsmall and event-local, then letauthoritycarry the runtime force of that decision.For
UserPromptSubmit, the decision can stay:allowwarnblockbecause the event is user-initiated and the terminal boundary is the prompt attempt.
For mid-turn/headless events, I would avoid adding a fourth decision value if
authoritycan express the defer case cleanly:decision: warn+authority: advisory= visible warning, run may continuedecision: warn+authority: authoritative= defer / needs-human, run must not proceed until an authorized actor resolves itdecision: block+authority: authoritative= terminal denial for that boundaryThe acceptance test I would add is the headless one:
PreToolUsefires in a headless/non-interactive run.decision: warn,authority: authoritative,terminal_for: tool_call,resume_capable: true.That preserves the simple prompt-admission enum while still giving headless runs a real non-execution state that is stronger than a soft warning and less final than a block.
Hook parity needs a typed event matrix backed by replay fixtures.
For each hook, keep event name, payload schema version, decision authority, blocking/warn behavior, resume/compaction behavior, and sample real payloads. Compatibility tests can then replay Claude-style fixtures and Codex-native fixtures so parity means stable semantics, not only matching names.
---
_Generated with ax._
Empirical update relevant to the hook parity question. v0.9 of world-model-mcp shipped today with a pre-registered SWE-bench Verified benchmark on the Claude Code side of the parity gap. Lifecycle-hook-based persistent memory reduced repeated coding-agent failures by +10.2 pts combined paired delta across 49 instances. Within-domain +15.0 pts, cross-domain +6.9 pts with zero cross-domain regressions.
The methodologically clean piece for this thread: the same contract elements that need parity on Codex (typed events, decision/authority semantics, blocking PreCompact, payload schema versioning) are exactly what the Claude Code lifecycle hooks delivered to make this measurable. The +6.9 pts cross-domain transfer was achieved without any per-instance harness changes, only via the constraint extraction and re-injection layer the contract enables.
If the Codex side ships event-name parity without contract parity (the @rpelevin authority leg, the @Necmttn payload schema versioning), a memory layer cannot run this test on Codex. The empirical case for parity at the contract level, not just the event-name level, is what this benchmark establishes.
Full result: https://github.com/SaravananJaichandar/world-model-mcp/blob/main/benchmarks/repeat-mistake/RESULTS.md
This umbrella issue maps closely to the vendor-neutral Trusted Runtime contracts we are developing in LS.
One useful design principle may be:
hook event
≠ model-visible notification
≠ authorization
≠ execution
A canonical hook schema should make those boundaries explicit rather than relying on handler-specific conventions.
A possible versioned event envelope:
{
"schema_version": "agent-hook-event/v0.1",
"event_id": "evt-001",
"event_type": "PreToolUse",
"trajectory_id": "project-task-trajectory",
"continuation_id": "current-thread-continuation",
"actor": {
"session_id": "thread-123",
"agent_id": "agent-456",
"role": "implementer",
"parent_agent_id": "agent-root"
},
"subject": {
"tool_family": "file_mutation",
"tool_name": "internal_patch",
"target_refs": ["src/example.py"]
},
"context_refs": [
"constraint:no-production-write",
"decision:approved-scope"
],
"decision": {
"status": "HOLD",
"reason_code": "ACTIVE_CONSTRAINT_CONFLICT",
"model_visible": true,
"execution_authorized": false
},
"audit": {
"durable": true,
"transcript_visible": false
}
}
This would help unify several requirements already listed in the issue:
A few invariants may be useful:
«Every side-effecting operation must emit a matchable event, including internal tools that are not exposed under user-facing names.»
«Hook output may inform the model, but model visibility must not imply authorization.»
«A blocking decision must be durable and replayable, not only displayed in the TUI.»
«Resume and compaction should preserve the same trajectory while creating a new continuation identity.»
«Subagent events should include runtime identity, parent lineage, and terminal status so claimed work can be matched to actual execution.»
The same envelope could support:
PreCompact
→ preserve active constraints and operational tail
PostCompact
→ validate recovered state before injection
SubagentStart / SubagentStop
→ bind delegation to runtime execution receipts
PermissionRequest
→ produce an explicit approval record
PreToolUse
→ ALLOW / HOLD / BLOCK before effect
PostToolUse
→ typed observed outcome and evidence refs
A small conformance suite could test:
Related LS work:
https://github.com/safal207/LS/pull/651
https://github.com/safal207/LS/issues/594
https://github.com/safal207/LS/issues/595
https://github.com/safal207/LS/issues/596
https://github.com/safal207/LS/issues/597
Would a small vendor-neutral "AgentHookEvent" conformance fixture be useful for testing parity across Codex surfaces without prescribing the internal hook implementation?
A small vendor-neutral conformance fixture would be useful if it tests the contract boundaries rather than only the event names.
The key split I would preserve is:
hook event is not the same thing as model-visible notification;
model-visible notification is not the same thing as authorization;
authorization is not the same thing as execution;
execution is not the same thing as audit durability.
For a fixture, I would make each event carry three independent axes:
The acceptance tests I would pin first:
That keeps the fixture vendor-neutral while making parity testable across CLI, Desktop, IDE, plugins, and worktrees. The fixture does not need to prescribe the internal hook implementation; it only needs to define the externally observable contract a runtime must satisfy.
Boundary: architecture and test feedback only; no claim about using this project or running its code.
@rpelevin This separation is exactly right. I would make the first fixture encode the three axes independently rather than infer one from another:
The mandatory invariant would be:
«Observation or visibility never implies authority, and a post-effect record never becomes retroactive authorization.»
A minimal first vector could be "blocked_internal_patch":
This composes cleanly with the RAMR ↔ LS boundary we recently froze: reliability determines what state is recovered, while conformance determines whether this exact action may execute.
RAMR canonical fixture:
https://github.com/DanceNitra/ramr/blob/main/fixtures/ramr_ls/duplicate_successful_outcome.json
LS pinned verifier:
https://github.com/safal207/LS/blob/main/tools/run_ramr_ls_shared_evidence_fixture.py
The next concrete artifact can be a small vendor-neutral JSON Schema with three or four deterministic fixtures, without prescribing Codex’s internal hook implementation.
Boundary: architecture and test feedback only; no claim about using this project or running its code.
That first vector is the right one. I would make the fixture fail unless all three axes are independently present and independently checked.
For blocked_internal_patch, the expected record shape should be explicit:
The verifier should reject if:
I would keep recovered-state and orientation references out of the core fixture except as optional evidence provenance. The portable contract should stand alone: recovered state can explain what is known, but it cannot authorize the next action.
Boundary: architecture and test feedback only; no claim about using this project or running its code.
Thanks — agreed. The fixture should fail unless observation, authority, and visibility/durability are independently present and independently verified.
I’ll keep recovered-state and orientation references outside the portable core, as optional provenance only.
For "blocked_internal_patch", I’ll bind the pre-effect observation and BLOCK decision to the same actor, continuation, target refs, action digest, and decision identity, then add explicit rejection cases for:
The invariant stays narrow:
recovered knowledge may explain state
!= recovered knowledge authorizes action
That gives the fixture a cleaner vendor-neutral boundary.
The discussion now has a concrete implementation target:
observation,authority, andvisibility_durabilityare independent and independently verified;blocked_internal_patch;The next useful contribution is a concrete vendor-neutral JSON Schema and deterministic fixtures rather than further abstract discussion.
Boundary: architecture and test feedback only; no claim about Codex adoption or implementation.
For the first AgentHookEvent artifact, I would make the schema validate three independent blocks and make the verifier reject if one block is inferred from another.
Minimal shape:
{
"schema_version": "agent-hook-event/v0.1",
"fixture_id": "blocked_internal_patch",
"observation": {
"event_id": "evt-001",
"event_type": "PreToolUse",
"trajectory_id": "traj-001",
"continuation_id": "cont-001",
"actor_id": "agent-root",
"parent_actor_id": null,
"subject": {
"tool_family": "file_mutation",
"tool_name": "internal_patch",
"target_refs": ["src/example.py"],
"args_digest": "sha256:..."
}
},
"authority": {
"decision_id": "dec-001",
"status": "BLOCK",
"reason_code": "ACTIVE_CONSTRAINT_CONFLICT",
"binding": {
"actor_id": "agent-root",
"continuation_id": "cont-001",
"target_refs": ["src/example.py"],
"args_digest": "sha256:..."
},
"validity": {
"issued_at": "2026-06-25T00:00:00Z",
"expires_at": null
},
"execution_authorized": false
},
"visibility_durability": {
"model_visible": true,
"user_visible": true,
"audit_persisted": true,
"survives_resume": true,
"survives_compaction": true,
"transcript_visible": false
}
}
I would pin the first fixture set around that object:
The verifier rule is simple: observation says what was proposed; authority says whether that exact action may proceed; visibility_durability says who sees it and what persists. Passing one block never satisfies another, and recovered context remains provenance only, never execution authority.
Boundary: architecture and test feedback only; no claim about Codex adoption, implementation, or running code.
@rpelevin Agreed — this is the right first artifact boundary.
I would make
AgentHookEvent/v0.1a conformance object with the three blocks required independently:observation: what action/event was proposed, including event id/type, trajectory id, continuation id, actor, parent lineage, subject, target refs, tool/action, and args digest;authority: whether that exact proposed action may proceed, includingALLOW | HOLD | BLOCK | RESUME, decision id, reason, exact binding fields, validity window, and policy refs;visibility_durability: who saw the decision and what survives, including model-visible, user-visible, audit-persisted, survives-resume, survives-compaction, and evidence refs.The verifier should reject whenever one axis is inferred from another rather than explicitly present. In particular:
I can prepare a small vendor-neutral fixture pack around this shape: one valid
blocked_internal_patchvector plus negative vectors for missing authority, inferred visibility, changed binding on resume, stale recovered policy after compaction, and post-effect audit being treated as retroactive authorization.That keeps the first artifact testable across CLI/Desktop/IDE/plugin runtimes without requiring any claim about Codex internals or adoption.
Boundary: architecture and test feedback only; no claim about using this project or running its code.
Yes. I would keep the first fixture pack small and make it prove axis independence rather than try to model every hook surface.
For AgentHookEvent/v0.1, the conformance rule I would want is:
That gives the fixture pack a useful shape:
The important part is that the valid case should be boring and the negative cases should be impossible to hand-wave. A runtime can then implement hooks however it wants, but the portable artifact still proves the same invariant: proposed, authorized, visible, and durable are separate checks.
Boundary: architecture and test feedback only; no claim about Codex adoption, implementation, or running code.
@rpelevin Agreed. Keeping v0.1 small and making it prove axis independence is the right first boundary.
I also think the next useful extension point is temporal phase rather than more hook-surface breadth. The same observation / authority / visibility-durability fields should not be treated as timeless booleans: their force changes across the lifecycle of a trajectory.
So for the first fixture pack I would keep your shape exactly as-is, but leave room for a later phase-aware profile on top of it:
Concretely, I would keep
AgentHookEvent/v0.1limited to the axis-independence vectors you outlined, then add a later temporal-phase fixture pack around the same contract surface: fresh authority, resumed authority, stale authority, expired-but-auditable authority, and revalidated authority.That keeps v0.1 boring in the good way, while still giving the contract a clean path to model continuity over time instead of flattening everything into static booleans.
Boundary: architecture and test feedback only; no claim about Codex internals or adoption.
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
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_helperandinject_helperPython 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.executableand 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.0I 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.
I’d like to propose a focused implementation of the missing
PermissionDeniedlifecycle 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
PermissionDeniedas a first-class hook event.Denied,TimedOut, andAbort, plus non-allow network-policy amendment results.decision_sourceas the matcher input:auto_reviewuserhook^auto_review$without hard-coding source suppression into the runtime.The proposed command input includes the normal session/turn/cwd/model context plus:
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
PermissionRequestcontract.Prototype validation
codex doctorreports 17 checks OK with no warnings or failures outside the harness sandbox.PermissionDeniednotification.just testrun 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:
auto_review,user, andhookthe desired external source labels?decision = "denied"?I’m happy to adapt the prototype to the maintainers’ preferred payload, naming, staging, and test requirements.
world-model-mcp (OSS memory MCP) already ships a Codex adapter. The hook events it hard-depends on today are
PostToolUseandUserPromptSubmit. 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. Thecontent_type: rule | fact | procedurerouting 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, andsession_idis 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.