Skip to content

feat(hooks): analyze and format every file an apply_patch touched - #174

Closed
ryzizub wants to merge 3 commits into
mainfrom
feat/apply-patch-file-paths
Closed

ryzizub wants to merge 3 commits into
mainfrom
feat/apply-patch-file-paths

Conversation

@ryzizub

@ryzizub ryzizub commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Description

Codex edits through apply_patch, not Edit/Write. Its payload carries the patch as text and never sets tool_input.file_path, so analyze.sh and format.sh fired on every Codex edit and did nothing.

Both now read their files through one shared payload_file_paths helper:

  • tool_input.file_path when present — Claude Code, unchanged
  • otherwise the A path / M path lines of an apply_patch tool_response — Codex
  • D path is skipped, a rename reports its new name as M
  • a relative path is resolved against the payload cwd, since Codex echoes it exactly as the model wrote it
  • tool_response is a string on Codex and an object on Claude; the scan is guarded with strings

A patch touching several files now analyzes and formats each. analyze.sh keeps going after a failure so the agent sees every issue, then exits 2.

Both scripts gain tests. CI has no Dart SDK, so they stub dart and assert which files it was asked to handle, like the existing tests stub very_good. Codex fixtures use tool_response text captured from codex-cli 0.153.4.

Verified

  • 25 new assertions pass; the four existing hook suites still pass (36/48/14/12).
  • Real dart against real captured Codex payloads: the relative-path Add reports an unused local and exits 2; the Update reformats the file.
  • On Codex the file list exists only on PostToolUse, which is where both hooks run.

Context: VeryGoodOpenSource/vgv_ai_cli#51 translates the hooks into ~/.codex/hooks.json; with this change the scripts work there without a payload wrapper.

Type of Change

  • New feature (feat)
  • Bug fix (fix)
  • Code refactor (refactor)
  • Documentation (docs)
  • CI change (ci)
  • Chore (chore)

🤖 Generated with Claude Code

ryzizub and others added 3 commits October 6, 2026 07:48
Codex edits through apply_patch rather than Edit or Write. Its hook payload
carries the whole patch as text and never sets tool_input.file_path, so
analyze.sh and format.sh fired on every edit and did nothing: the field they
read was empty, and they exited 0 having analyzed and formatted nothing.

Both now read their files through a shared payload_file_paths helper. It
takes tool_input.file_path when present, as Claude Code sends it, and
otherwise the "A path" and "M path" lines of an apply_patch tool_response,
which is what Codex reports after the tool has run. A deleted file ("D path")
is left out, a rename reports its new name as "M", and a relative path is
resolved against the payload's cwd, since Codex echoes the path exactly as
the model wrote it. Codex's tool_response is a string and Claude Code's is an
object, so the scan is guarded with `strings` and the Claude Code path is
unchanged.

A patch touching several files analyzes and formats each. analyze.sh keeps
going after a failure so the agent sees every issue at once, then exits 2.

Both scripts gain the tests they never had. CI has no Dart SDK, so the tests
stub `dart` on PATH and assert which files it was asked to handle, the way the
existing tests stub very_good. The Codex fixtures use the tool_response text
captured from codex-cli 0.153.4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The "without jq" cases gave the hook a PATH of the dart stub plus /bin,
counting on /bin having cat but not jq. That holds on macOS and fails on
Ubuntu, where /bin is a symlink to /usr/bin and so still reaches jq; the hook
found it, ran, and the assertions that it stood aside failed in CI.

The PATH is now built from a directory holding only a symlink to cat, the one
coreutil the hook uses before it checks for jq, so what is on it no longer
depends on how the host lays out /bin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`env -i PATH=... bash "$HOOK"` resolves bash with the PATH it was just given.
That worked while /bin was on it and broke the moment the no-jq PATH was
narrowed to a directory holding only cat: env could not find bash at all and
exited 127, which the "exits 0 when jq is missing" assertions caught.

bash is now captured once by absolute path and invoked that way, so the hook's
PATH can be as narrow as a case needs without taking the interpreter with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ryzizub

ryzizub commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #175: the Codex payload parsing moves to vgv_ai_cli's translator, and the scripts take their files as arguments instead.

@ryzizub ryzizub closed this Oct 6, 2026
@ryzizub
ryzizub deleted the feat/apply-patch-file-paths branch October 6, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant