Codex hook support — design
Codex hook support — design
Date: 2026-05-27
Status: approved (pending spec review)
Branch base: feat/mcp-tool-normalization
Goal
Let Claudio play contextual sounds for OpenAI Codex CLI, the same way it already does for Claude Code. A user running Codex should be able to install Claudio hooks and hear loading, success, error, and lifecycle sounds.
Background
Codex CLI shipped stable hooks in May 2026. The contract is close enough to Claude Code that most of Claudio works unchanged:
- Every command hook receives one JSON object on stdin (same as Claude Code).
- Shared field names:
session_id,cwd,hook_event_name,tool_name,tool_input,tool_response,prompt. - Hook config lives in
~/.codex/hooks.json(or inline inconfig.toml), with top-level shape{"hooks": {EventName: [ {matcher, hooks:[{type,command}]} ]}}— structurally the same as Claude’ssettings.jsonhooks block. - A hook that exits 0 with no stdout is treated as success and execution continues. Claudio’s detached worker already exits 0 silently, so it never blocks Codex.
Sources:
- https://developers.openai.com/codex/hooks
- https://developers.openai.com/codex/config-advanced
Codex hook events
SessionStart, SubagentStart, PreToolUse, PermissionRequest,
PostToolUse, PreCompact, PostCompact, UserPromptSubmit, SubagentStop,
Stop.
Codex has no Notification or SessionEnd event. Claude has no SubagentStart
or PostCompact. Everything else overlaps.
Differences that drive the work
transcript_pathis nullable/omitted in Codex. Claudio’s parser currently rejects an event whentranscript_pathis empty (parser.go:124).- Install target is
~/.codex/hooks.json, not~/.claude/settings.json. - The Codex event set differs (adds
SubagentStart,PostCompact; dropsNotification,SessionEnd). - Codex tool names include
apply_patch(Claudio has no special case; flows through the generic path) andmcp__<server>__<tool>(already normalized tomcpby the prior commit on this branch). - Non-managed Codex hooks require explicit trust via the
/hookscommand. Claudio cannot bypass this; it surfaces a reminder after install.
Decisions
- Scope: full parity. Map the Codex-only events and tools, not just the shared ones.
- Install format:
~/.codex/hooks.json(pure JSON, reuses existing merge logic, no TOML dependency, never touches the user’sconfig.toml). - Agent selection:
--agent/-aflag oninstallanduninstall, valuesclaude(default) andcodex. - Sound assets: mapping logic only. New Codex-only hints rely on the
5-level fallback until custom audio is added. No new
.wavfiles. - Test fixtures: inferred from the documented schema. Verify against real Codex payloads later.
Architecture
The core pipeline — parse → GetContext → SoundMapper → audio backend — is
reused unchanged. Two areas change: the hook parser gains tolerance and new
event cases; the install/uninstall layer becomes agent-aware.
Component 1 — Parser tolerance (internal/hooks/parser.go)
- Stop treating an empty
transcript_pathas a fatal validation error. Keepsession_id,cwd, andhook_event_namerequired (Codex always sends all three). - Add
GetContextcases:SubagentStart→ categoryLoading, hintsubagent-start, operationsubagent-start.PostCompact→ categorySystem, hintpost-compact, operationpost-compact.
- No change needed for
PermissionRequest,SessionStart,PreCompact,Stop,SubagentStop— already mapped. apply_patchflows through the existing generic<tool>-start/<tool>-success/<tool>-errorpath. The 5-level fallback covers any missing audio.- Success/error detection reuses
analyzeToolResponse:isError,stderr, andinterruptedchecks, defaulting to success when none are present. This matches the documented Codextool_response(“MCP result or output”) as closely as the docs allow; revisit when real payloads are available.
This component is self-contained: input is a JSON byte slice, output is a
HookEvent and an EventContext. It has no knowledge of which agent produced
the event.
Component 2 — Agent abstraction (internal/install)
Introduce an Agent value (claude |
codex) that selects three things: |
- Config path finder.
- Claude:
~/.claude/settings.json(existingFindBestSettingsPath, unchanged). - Codex:
~/.codex/hooks.json, with the same WindowsUSERPROFILE/ MSYS handling used for Claude.
- Claude:
- Hook registry.
- Claude registry: unchanged.
- Codex registry:
PreToolUse,PostToolUse,UserPromptSubmit,Stop,SubagentStop,SubagentStart,PreCompact,PostCompact,SessionStart,PermissionRequest.
- Default matcher. Codex
"*", Claude".*".
Reused without change because they operate on the generic hooks JSON key:
ReadSettingsFile, WriteSettingsFile, MergeHooksIntoSettings,
IsClaudioHook, and the atomic temp-file-plus-rename write.
The boundary stays clean: callers ask the agent for “where do I write” and “which events do I register,” then hand off to the existing generic merge/write functions.
Component 3 — CLI wiring (internal/cli)
- Add
--agent/-atoinstallanduninstall. Defaultclaude. Validate against the known set; reject anything else with a clear error. - The install workflow takes the agent, resolves the path finder and registry, and runs the existing read → generate → merge → write → verify steps.
- After a successful Codex install, print:
Run /hooks in Codex to trust the claudio hook. --dry-runand--printshow the selected agent and the resolved config path; the Codex dry-run also shows the trust reminder.
Data flow (Codex)
- Codex fires an event and spawns
claudiowith the event JSON on stdin. - Claudio reads the payload, spawns a detached worker, and exits 0 with no stdout. Codex sees success and continues — never blocked.
- The worker parses the payload, derives category and sound hint, and plays the resolved sound through the 5-level fallback.
Error handling
- Parser: a null or missing
transcript_pathis no longer fatal. Unknown events fall through to the existing default (Interactive,defaultsound). - Installer: a missing
hooks.jsonis created (ReadSettingsFilereturns empty settings). The merge preserves any non-Claudio Codex hooks already present. Writes stay atomic. - Trust: Claudio cannot auto-trust a non-managed hook. The reminder is the mitigation.
Testing
Strict TDD: red first, then minimal implementation, then refactor. Fixtures are built from the documented Codex schema.
Coverage targets
Current baseline (this machine):
internal/hooks80.2%internal/install81.0%internal/cli— tests do not build locally (cgo linkercannot find 'ld'via mingw; the package pulls in the audio/malgo cgo dependency).
Targets:
- Ratchet
internal/hooksandinternal/installto ≥ 90%. - New code (Codex parser cases, agent abstraction,
--agentflag handling) to ≥ 95% with branch coverage on each new decision point. - Coverage ratchets up, never down (RULES always-on rule).
Test cases
Parser (internal/hooks/parser_test.go):
- Event with null
transcript_pathparses successfully. - Event with omitted
transcript_pathparses successfully. - Still rejects missing
session_id,cwd, orhook_event_name. SubagentStart→Loading/subagent-start.PostCompact→System/post-compact.apply_patchPreToolUse→apply_patch-start;PostToolUsesuccess →apply_patch-success; withisError→ error category.mcp__server__toolevent → normalized tomcp(regression guard).
Install (internal/install):
- Codex path finder resolves
~/.codex/hooks.json(and WindowsUSERPROFILE). - Codex registry contains exactly the ten Codex events and no
Notification/SessionEnd. - Generate + merge Codex hooks into an empty
hooks.json(afero in-memory). - Merge preserves a pre-existing non-Claudio Codex hook.
- Idempotent re-install does not duplicate Claudio entries.
- Default matcher is
"*"for Codex.
Uninstall (internal/uninstall):
--agent codexremoves only Claudio entries, preserves others.
CLI (internal/cli):
install --agent codex --dry-runshows the Codex path and trust reminder.install --agent codex --printshows agent + path.- Invalid
--agentvalue is rejected. - Default
--agentisclaude(no behavior change for existing users).
Note: if the local cgo linker stays broken, CLI-package tests run in CI or on a
machine with a working ld. The hooks and install packages have no cgo
dependency and test locally.
Out of scope (YAGNI)
config.toml/ inline TOML hook installation.- New audio assets for Codex-only hints.
- Codex structured outputs (deny / rewrite /
updatedInput). Claudio is an observer; it never alters Codex behavior. - Auto-detecting installed agents.