Codex hook-path portability audit (2026-09-25)¶
Question¶
Why did every repository-local Codex hook stop executing after the active fork moved from the retired vmaf checkout to VMAFx/vmafx, and what command form survives linked worktrees and future checkout moves?
Evidence¶
- Commit
5730fca22first tracked.codex/hooks.jsonwith all seven commands bound to/home/kilian/dev/vmaf/.codex/hooks/. That directory no longer exists; the active checkout is resolved bygit rev-parse --show-toplevelafter clearing inherited repository-local Git variables. - Codex CLI 0.145.0 reports the
hooksfeature as stable and consumes the tracked JSON shape already present in this repository. - The seven target scripts remain tracked with Git mode
100755under.codex/hooks/; only command resolution was broken. - A regression test written before the fix failed on every exact mapping. The safe pre-tool script itself succeeded when invoked through the proposed repository-root command from
core/src/.
The defect was therefore configuration path capture, not a missing script, permission bit, hook event, or Codex feature flag. A JSON parser could not catch it because the stale absolute strings were valid JSON and valid shell tokens.
Alternatives considered¶
| Option | Result |
|---|---|
| Replace the retired path with the current absolute checkout | Rejected: it breaks again on another clone, user account, or linked worktree. |
Use .codex/hooks/<script>.sh directly | Rejected: hook launch directories are not a stable repository-root contract. |
Clear repository-local Git variables, then resolve git rev-parse --show-toplevel inside a quoted command | Selected: Git supplies the active main or linked-worktree root, inherited hook context cannot redirect it, and quoting preserves spaces. |
| Copy hooks into a user-global directory | Rejected: it separates executable state from the reviewed repository revision. |
No new ADR is needed: this is a one-way correction of a broken repository path, not a new architectural or policy choice.
Regression boundary¶
scripts/ci/tests/test_codex_hook_config.py fails closed on:
- any change to the seven event, matcher, type, or script mappings;
- duplicate or extra hook entries;
- anything other than the quoted Git-root command form;
- missing targets or a tracked mode other than
100755; and - failure to execute the safe pre-tool hook from a nested directory while a misleading inherited
GIT_WORK_TREEpoints at that directory.
The dedicated test-codex-hook-config pre-commit/pre-push entry runs in the required Pre-Commit CI job through pre-commit --all-files. Session-start is not executed by the unit test because it may fetch upstream; the contract instead verifies its exact path and executable mode without a network side effect.
Scope¶
The fix changes repository-local Codex configuration, its regression gate, and operator documentation only. It does not change libvmaf code, numerical output, models, training, benchmarking, dependency versions, public C/CLI surfaces, or the FFmpeg patch stack. Netflix golden assertions are untouched.