Skip to content

feat(codex): add hook conversion support for 5 Codex lifecycle events#354

Draft
tmchow wants to merge 1 commit intomainfrom
feat/codex-pretooluse-hooks
Draft

feat(codex): add hook conversion support for 5 Codex lifecycle events#354
tmchow wants to merge 1 commit intomainfrom
feat/codex-pretooluse-hooks

Conversation

@tmchow
Copy link
Copy Markdown
Collaborator

@tmchow tmchow commented Mar 23, 2026

Summary

The Codex converter has been silently dropping all hooks during Claude-to-Codex conversion since the converter was first written. Every other converter (Windsurf, Kiro, Copilot, Gemini) at minimum emits console.warn when hooks are skipped -- the Codex converter had zero hook handling at all.

Meanwhile, Codex has been incrementally shipping hook support across multiple PRs, creating a growing conversion gap:

  • PreToolUse -- merged (#15211), shell/Bash only, deny-only
  • PostToolUse -- draft (#15531), shell/Bash only, block + additionalContext
  • UserPromptSubmit -- merged (#14626), block + additionalContext, no matchers
  • SessionStart -- exists on Codex main
  • Stop -- exists on Codex main

This PR closes the gap by converting compatible hooks and emitting specific warnings for everything unconvertible.

Why this matters

Plugins with security-critical hooks (e.g., PreToolUse deny hooks that block dangerous commands) or workflow hooks (e.g., UserPromptSubmit context injection) lose that protection silently when converted to Codex. Users get no indication their hooks were dropped. This is especially concerning for PreToolUse hooks that enforce safety policies.

Changes

  • Types (src/types/codex.ts): Added CodexHookEventName string literal union for compile-time safety against invalid events (critical given Codex's deny_unknown_fields), plus CodexHookCommand, CodexHookMatcher, CodexHooks types and hooks field on CodexBundle
  • Converter (src/converters/claude-to-codex.ts): Added CODEX_EVENTS map with toolScoped flag, isBashCompatibleMatcher() helper, and convertHooksForCodex() that filters to compatible hooks and emits specific warnings for everything skipped
  • Writer (src/targets/codex.ts): Writes .codex/hooks.json with backup support, auto-enables [features] codex_hooks = true in config.toml, refactored renderCodexConfig() to options object
  • Sync path (src/sync/codex.ts): Updated renderCodexConfig call site for new signature (backward compatible)
  • Spec (docs/specs/codex.md): Added Hooks section with supported events, format, converter behavior, and constraints
  • Research (docs/solutions/integration-issues/codex-hook-converter-gap.md): Full gap analysis with upstream PR links and compatibility matrix
  • Plan (docs/plans/2026-03-23-001-feat-codex-hook-conversion-beta-plan.md): Implementation plan (completed)

Key decisions

  • PostToolUse included but depends on #15531 -- hold this PR until that upstream PR merges to avoid a second implementation pass
  • Wildcard matchers on tool-scoped events are converted (Codex PreToolUse/PostToolUse only fire for Bash anyway)
  • Mixed matchers like Bash|Write are skipped entirely with warning
  • Feature gate ([features] codex_hooks = true) written at top of config.toml to stay outside sync path's managed block markers
  • No content transformation on hook commands -- they are shell commands, not markdown

Test plan

  • 12 new converter tests covering all 5 events, matcher filtering, type filtering, warnings, edge cases
  • 6 new writer tests covering hooks.json output, backup, feature gate, config.toml combinations
  • All 38 codex-related tests pass
  • Sync path tests pass (backward-compatible signature change)
  • bun run release:validate passes
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant