feat(codex): mirror Phase 1 hooks to ~/.codex via curated allowlist (ADR-182)#374
Merged
feat(codex): mirror Phase 1 hooks to ~/.codex via curated allowlist (ADR-182)#374
Conversation
…ADR-182)
Extend install.sh to mirror a curated allowlist of 6 hooks to ~/.codex/hooks/,
generate ~/.codex/hooks.json from the allowlist, and set the codex_hooks
feature flag in ~/.codex/config.toml.
Phase 1 hooks (functional + Codex-compatible, verified against source):
SessionStart: kairos-briefing-injector, operator-context-detector,
team-config-loader, rules-distill-injector
Stop: session-learning-recorder
PostToolUse: posttool-bash-injection-scan (matcher=Bash)
Phase 2 hooks (Edit/Write interceptors) deliberately excluded until
openai/codex#16732 ships upstream. Codex PreToolUse/PostToolUse currently
fire only for the Bash tool; a silently-registered hook is worse than a
missing one because users assume it is protecting them.
New scripts:
scripts/codex-hooks-allowlist.txt authoritative Phase 1 list
scripts/generate-codex-hooks-json.py allowlist to hooks.json generator
scripts/ensure-codex-feature-flag.py TOML-aware config.toml merger
New tests (50 passing, 1 intentional skip):
test_generate_codex_hooks_json.py 16 tests schema + CLI
test_ensure_codex_feature_flag.py 21 tests TOML merge + idempotency
test_codex_hooks_allowlist.py 6 tests regression guard against
Phase 2 promotion
test_codex_hooks_install.py 7 + 1s end-to-end install.sh
verification in tempdir HOME
README.md gets a Codex CLI Parity section documenting what mirrors, what
does not, and the openai/codex#16732 upstream blocker.
Verified: install.sh tempdir run populates ~/.codex/hooks/ with 6 Phase 1
hooks plus hooks/lib/, generates valid hooks.json with correct event
grouping (SessionStart matcher startup|resume, PostToolUse matcher Bash),
and sets config.toml feature flag while preserving existing [projects.*],
[plugins.*], and [notice] sections.
The test (3.10) job was failing because tomllib is stdlib only in Python 3.11+. Three changes: 1. scripts/ensure-codex-feature-flag.py: remove the unused `import tomllib`. The script uses pure string/regex operations for both read and write; the import was dead code. 2. scripts/tests/test_ensure_codex_feature_flag.py: wrap the tomllib import in try/except and define a `requires_tomllib` skip marker. Apply the marker to the 9 tests that parse output with tomllib.loads(). The 12 tests that verify the write logic via string/regex checks run on 3.10+ unchanged. 3. scripts/tests/test_codex_hooks_install.py: same pattern. Apply `requires_tomllib` to the 3 tests that parse the post-install config.toml. The 4 tests that verify files exist, hooks.json shape, and dry-run behavior run on 3.10+ unchanged. Verified locally by simulating the 3.10 ImportError via sys.modules manipulation: test modules collect and import successfully, decorated tests are correctly skipped on 3.10 while non-decorated tests still run. On Python 3.13 the full suite still passes (50 passed, 1 intentional skip).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extend
install.shto mirror a curated allowlist of 6 Phase 1 hooks to~/.codex/hooks/, generate~/.codex/hooks.jsonfrom the allowlist, and set thecodex_hooksfeature flag in~/.codex/config.toml. This gives Codex CLI sessions the same session-start injection and Bash-scanning governance that Claude Code sessions already have.Implements ADR-182. The ADR (
adr/182-codex-hooks-mirror.md, local only) was revised mid-implementation after an audit revealed the original draft Phase 1 list had misclassified 3 Edit/Write interceptors as safe. The corrected allowlist and the regression guard test catch this class of error automatically going forward.Why a curated allowlist, not wholesale mirror
OpenAI Codex CLI v0.114.0 added experimental hook support with a schema nearly identical to Claude Code's, BUT openai/codex#16732 (still open as of April 2026) limits
PreToolUse/PostToolUsehooks to fire only for theBashtool. Tool calls throughapply_patch,Write,Edit, MCP, andWebSearchdo not trigger hooks.A wholesale mirror of
hooks/*.pywould register every Edit/Write interceptor on Codex, where they would never fire. Silently-registered-but-never-invoked hooks are worse than missing hooks because users assume the hook is protecting them. The allowlist approach is explicit inclusion with a regression guard to prevent accidental Phase 2 promotion.Phase 1 hooks (shipping now)
kairos-briefing-injector.pystartup|resumeoperator-context-detector.pystartup|resumeteam-config-loader.pystartup|resumerules-distill-injector.pystartup|resumesession-learning-recorder.pyposttool-bash-injection-scan.pyBashPhase 2 hooks (deliberately excluded)
Edit/Write interceptors:
adr-enforcement.py,pretool-config-protection.py,creation-protocol-enforcer.py,posttool-rename-sweep.py,pretool-plan-gate.py,pretool-unified-gate.py,pretool-adr-creation-gate.py,reference-loading-enforcer.py,reference-loading-gate.py, plus three hooks the ADR draft misclassified (pretool-prompt-injection-scanner.py,suggest-compact.py,sql-injection-detector.py) which are actuallyPreToolUse/PostToolUsewithWrite|Editmatchers.Phase 2 unblocks when
openai/codex#16732ships upstream.What's in this PR
New files:
scripts/codex-hooks-allowlist.txt(authoritative Phase 1 list with exclusion rationale as comments)scripts/generate-codex-hooks-json.py(allowlist to hooks.json generator, stdlib only)scripts/ensure-codex-feature-flag.py(TOML-awareconfig.tomlmerger with backup)New tests (50 passing + 1 intentional skip):
scripts/tests/test_generate_codex_hooks_json.py(16 tests: schema, CLI, determinism, event ordering, malformed input)scripts/tests/test_ensure_codex_feature_flag.py(21 tests: TOML merge, idempotency,codex_hooks=falseerror path, preservation of existing sections)scripts/tests/test_codex_hooks_allowlist.py(6 tests: regression guard against Phase 2 promotion. Scans hook source forEdit|Write|apply_patchmatcher patterns; tests that Phase 2 hook filenames from the ADR are NOT in the allowlist)scripts/tests/test_codex_hooks_install.py(7 tests + 1 skip: end-to-end install.sh verification against a tempdir HOME; mirrors files, generates valid hooks.json, sets feature flag, preserves existing config sections, uninstall symmetry)Modified:
install.sh(+129 lines:CODEX_HOOKS_DIRvariable,Syncing Codex hooks mirrorblock, uninstall cleanup, Codex version warning for <0.114.0)README.md(+24 lines: new## Codex CLI Paritysection explaining what mirrors, what does not, and the#16732upstream blocker)Test plan
pytest scripts/tests/test_codex_hooks_*.py scripts/tests/test_generate_codex_hooks_json.py scripts/tests/test_ensure_codex_feature_flag.py -v(50 passed, 1 skipped in 43s)ruff checkandruff format --checkon all new Python files (clean)bash -n install.sh(syntax OK)bash install.sh --dry-run --symlink(prints correct Codex hooks section, no side effects)HOME=\$TMP bash install.sh --copy --forcepopulates~/.codex/hooks/with all 6 Phase 1 files plushooks/lib/, generates valid hooks.json with correct matchers, sets[features] codex_hooks = truein config.toml while preserving existing sectionsbash install.sh --uninstallremoves~/.codex/hooks/, archiveshooks.jsonwith timestamp, leavesconfig.tomlfeature flag untouchedReferences
adr/182-codex-hooks-mirror.md, local only)