Codex has been incrementally shipping hook support (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop), but the Codex converter silently dropped all hooks during conversion -- the only converter that didn't even warn. This adds partial hook conversion for the 5 compatible events and specific warnings for everything unconvertible. - Add CodexHookEventName union, CodexHookCommand, CodexHookMatcher, CodexHooks types and hooks field to CodexBundle - Add CODEX_EVENTS map with toolScoped flag, isBashCompatibleMatcher() helper, and convertHooksForCodex() to the converter - Write .codex/hooks.json with backup support in the writer - Auto-enable [features] codex_hooks = true in config.toml when hooks present - Refactor renderCodexConfig() to options object for extensibility - Update Codex spec with hooks documentation - 18 new tests (12 converter + 6 writer) PostToolUse conversion depends on openai/codex#15531 merging.
8.6 KiB
title, type, status, category, component, created, updated, affected_files, tags
| title | type | status | category | component | created | updated | affected_files | tags | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Codex Hook Support: Cross-Platform Conversion Gap Analysis | research | active | integration-issues | codex-converter | 2026-03-23 | 2026-03-23 |
|
|
Codex Hook Support: Cross-Platform Conversion Gap Analysis
Problem Statement
The Codex converter (src/converters/claude-to-codex.ts) silently drops all hooks during Claude-to-Codex conversion with no warning emitted. Every other converter in the codebase (Windsurf, Kiro, Copilot, Gemini) at least emits console.warn for unsupported hooks. The CodexBundle type (src/types/codex.ts) has no hooks field, and the Codex spec (docs/specs/codex.md, last verified 2026-01-21) makes no mention of hooks.
Meanwhile, Codex has been incrementally shipping hook support across multiple PRs, creating a growing conversion gap.
Investigation
Step 1: Current Converter State
Checked src/converters/claude-to-codex.ts -- found zero references to hooks. The function convertClaudeToCodex() processes agents, commands, and skills but has no hook handling at all. No console.warn, no comment, nothing.
For comparison, every other converter follows a documented pattern from docs/solutions/adding-converter-target-providers.md (Phase 5):
if (plugin.hooks && Object.keys(plugin.hooks.hooks).length > 0) {
console.warn("Warning: {Target} does not support hooks. Hooks were skipped.")
}
Step 2: Codex Upstream Hook Support Research
Researched four Codex PRs and the current main branch to map what Codex now supports:
PreToolUse -- MERGED (openai/codex#15211)
- Shell/Bash only -- tool name hardcoded to
"Bash"regardless of underlying tool (shell,local_shell,shell_command,container.exec,exec_command) - Deny-only --
permissionDecision: "deny"is the only supported decision.allow,ask,updatedInput,additionalContextall fail open with an error log - Matchers are regex patterns tested against tool name (but tool name is always "Bash")
- Two blocking paths: JSON output with
permissionDecision: "deny", or exit code 2 + stderr - Key files:
codex-rs/hooks/src/events/pre_tool_use.rs,codex-rs/core/src/hook_runtime.rs
PostToolUse -- DRAFT/WIP (openai/codex#15531)
- Shell/Bash only (same as PreToolUse)
- Supports
blockdecision +additionalContextinjection updatedMCPToolOutputexists in the schema but is explicitly rejected at runtime- Input includes
tool_response(the command's output) in addition totool_input continue: false,stopReason,suppressOutputall present in schema but unsupported- Key files:
codex-rs/hooks/src/events/post_tool_use.rs,codex-rs/hooks/schema/generated/post-tool-use.command.input.schema.json
UserPromptSubmit -- MERGED (openai/codex#14626)
- Fires before user prompt reaches the model
- Supports
blockdecision +additionalContextinjection - No matcher support -- all registered hooks fire for every prompt (unlike Claude Code which supports matchers)
- Blocked prompts never enter conversation history (differs from Claude Code where they may still be recorded)
- Context injected as developer messages (not user messages)
- Feature-gated behind
codex_hooks = trueinconfig.toml[features] - Key files:
codex-rs/hooks/src/events/user_prompt_submit.rs,codex-rs/hooks/schema/generated/user-prompt-submit.command.input.schema.json
SessionStart -- EXISTS ON MAIN
- Supports
SessionStartSourceenum withStartupandResumevariants - Supports
additional_contexts(context injection) andshould_stop/stop_reason - A community PR (openai/codex#11637) proposed this but was closed due to OpenAI's contribution policy change -- OpenAI implemented it themselves
- Key files:
codex-rs/hooks/src/events/session_start.rs
Stop -- EXISTS ON MAIN
- Exists alongside SessionStart in the
HookEventNameenum - Fires at session end
Step 3: Hook Type Compatibility
Only command type hooks are supported in Codex. Claude Code's prompt and agent hook types have no equivalent.
Hook Event Compatibility Matrix
| Claude Event | Codex Status | Convertible? | Key Limitations |
|---|---|---|---|
| PreToolUse (Bash matcher) | Merged (PR #15211) | Yes | Deny-only; no allow/ask/updatedInput/additionalContext |
| PreToolUse (non-Bash) | N/A | No | Codex only fires for shell tools |
| PostToolUse (Bash matcher) | Draft (PR #15531) | Yes (pending merge) | Block + additionalContext only |
| PostToolUse (non-Bash) | N/A | No | Codex only fires for shell tools |
| UserPromptSubmit | Merged (PR #14626) | Yes | No matchers; blocked prompts don't enter history |
| SessionStart | Exists on main | Yes | Command-only |
| Stop | Exists on main | Yes | Command-only |
| PostToolUseFailure | N/A | No | No Codex equivalent |
| PermissionRequest | N/A | No | No Codex equivalent |
| Notification | N/A | No | No Codex equivalent |
| SessionEnd | N/A | No | No Codex equivalent |
| PreCompact | N/A | No | No Codex equivalent |
| Setup | N/A | No | No Codex equivalent |
| SubagentStart/Stop | N/A | No | No Codex equivalent |
Cross-Cutting Constraints
- Command-only: Only
type: "command"hooks. Nopromptoragenttypes in Codex. - Feature-gated: Requires
codex_hooks = trueinconfig.tomlunder[features]. - Strict schema:
deny_unknown_fieldson all wire types -- extra fields cause parse failure. - Fail-open design: Unsupported outputs, malformed JSON, non-zero/non-2 exit codes all resolve to "allow."
- No Windows support: Entire hook system disabled on Windows.
- No async hooks:
async: truein config is recognized but skipped with a warning.
Semantic Differences from Claude Code
- PreToolUse decisions: Claude Code supports allow/deny/ask/updatedInput/additionalContext. Codex supports deny only.
- UserPromptSubmit matchers: Claude Code supports matchers to selectively fire hooks. Codex has no matcher support.
- UserPromptSubmit blocking: In Claude Code, blocked prompts may still enter conversation history. In Codex, they never do.
- Context injection: Codex injects additionalContext as developer messages, which may have different model-visible behavior.
Recommended Solution
- Add
hooksfield toCodexBundletype insrc/types/codex.ts - Convert the compatible subset: PreToolUse (Bash-matched, command type), PostToolUse (pending PR #15531 merge), UserPromptSubmit, SessionStart, Stop
- Skip + warn for unconvertible hooks with specific reasons (non-Bash matchers, prompt/agent types, unsupported events)
- Write
hooks.jsonoutput in the Codex writer (src/targets/codex.ts) - Update
docs/specs/codex.mdwith hook documentation - Add converter and writer tests
Prevention Strategies
Silent feature drops
The core issue is that the Codex converter was never updated to even warn about hooks. Five converters (Codex, Droid, Pi, OpenClaw, Qwen) silently drop hooks. Consider:
- Adding a cross-converter completeness test that asserts every converter either includes hooks in its bundle or emits a warning
- Framework-level validation in
src/targets/index.tsthat automatically checks for dropped features post-conversion
Tracking upstream changes
The Codex spec (docs/specs/codex.md) was last verified 2026-01-21. Consider a capability matrix that maps each Claude feature to each target's support status with last-verified dates, and flagging entries older than 90 days as stale during release:validate.
Related Documentation
docs/specs/codex.md-- Codex target spec (needs hooks section added)docs/specs/claude-code.md-- Claude Code hook architecture referencedocs/solutions/adding-converter-target-providers.md-- Documented converter pattern (Phase 5 warns on unsupported features; Codex never implemented this)docs/solutions/codex-skill-prompt-entrypoints.md-- Codex-specific conversion patternssrc/converters/claude-to-opencode.ts-- Reference for full hook conversion (the only converter that maps hooks today)- Commit
598222e-- OpenCode PreToolUse try-catch fix (issue #85)
Refresh Candidates
docs/solutions/adding-converter-target-providers.mdmay need update once Codex hook conversion is implemented, since the pattern of "all new targets warn-and-skip hooks" will no longer be universaldocs/specs/codex.mdneeds a hooks section