Skip to content

feat(cli): give Codex shell searches graph context through a PreToolUse hook - #2719

Merged
DeusData merged 2 commits into
mainfrom
feat/codex-pretooluse-hook
Oct 25, 2026
Merged

DeusData merged 2 commits into
mainfrom
feat/codex-pretooluse-hook

Conversation

@DeusData

Copy link
Copy Markdown
Owner

Why

Claude Code's shell searches get graph context from the PreToolUse hook. Codex only had the SessionStart and SubagentStart reminders (#330), so its rg/grep calls never did (#715). Codex's hook contract matches Claude Code's for this case. In the openai/codex source:

  • Its shell tool is named Bash, and tool_input.command is a string (core/src/hook_runtime.rs).
  • PreToolUse hookSpecificOutput.additionalContext is collected for the model (hooks/src/events/pre_tool_use.rs, hooks/schema/generated/pre-tool-use.command.output.schema.json).

So hook-augment's existing default dialect handles Codex's payload unchanged; only the installer had to wire it.

What changes

  • config.toml. The managed block adds [[hooks.PreToolUse]] with matcher = "Bash", running the same hook-augment command. The previous block (the lifecycle pair only, as installs up to v0.12 wrote it) stays recognized as owned, so an upgrade replaces it in place and uninstall removes either generation.
  • Inline forms (Codex: reinstall appends duplicate [[hooks.SessionStart]] when hook exists in inline form — "duplicate key" breaks config.toml #1432). If Codex has rewritten our PreToolUse inline, it is replaced. A user's own inline hooks.PreToolUse is kept, and our block then leaves PreToolUse out, because TOML can't hold an inline array and array-of-tables entries for the same key. Install says so instead of failing.
  • hooks.json. A Codex-only PreToolUse Bash entry is added and removed next to the lifecycle pair, touching only the entry whose command is ours. The shared lifecycle helper used by Qwen and others is unchanged.
  • Install output names the hooks it actually wrote.
  • Re-trust. Codex asks users to re-trust changed hook definitions (/hooks).

Proof on real output

Built binary, isolated HOME/CODEX_HOME/cache:

step result
fresh install -y --clients=codex block = SessionStart + SubagentStart + PreToolUse Bash; output hooks: SessionStart + SubagentStart + PreToolUse Bash
previous v0.12 block in place (byte-exact), re-install rc 0; the block is replaced in place by the current one
Codex-shaped PreToolUse Bash payload (session_id, turn_id, null transcript_path, model, permission_mode, tool_use_id) hookSpecificOutput.hookEventName = PreToolUse with the graph symbols in additionalContext

Tests

  • New: cli_codex_pretooluse_hook_lifecycle. Covers fresh install, idempotence, removal, upgrade of the exact previous block, uninstall of it, a foreign inline PreToolUse that is kept, and our own inline form being replaced.
  • New: cli_hook_augment_codex_pretooluse_payload. Characterization of Codex's payload shape.
  • Extended: cli_codex_migrates_to_single_hook_representation. The hooks.json path must now carry PreToolUse, and uninstall must leave no hook-augment behind.
  • Updated expectation, in its own commit: config_toml_codex_accepts_v0102_managed_windows_crlf. Re-upserting over the v0.10.2 lifecycle-only block used to leave it byte-identical. That block is now the previous generation, so the upsert accepts it as owned and upgrades it in place. The test now pins the upgrade (one PreToolUse table, BOM, CRLF and the surrounding tables kept) and that a second upsert is byte-identical.
  • Unchanged: the suite's other previous-generation fixtures (markerless and damaged-pair recognition) still pass, which proves backward recognition.
  • Revert check: with PreToolUse rendering and the hooks.json entry disabled, exactly cli_codex_pretooluse_hook_lifecycle, cli_codex_migrates_to_single_hook_representation and config_toml_codex_accepts_v0102_managed_windows_crlf fail; with the change, all pass.
  • Local runs (macOS arm64, ASan/UBSan test runner): cli 365 passed / 0 failed, config_toml_edit 49 / 0, agent_clients 38 / 0.
  • Lint: clang-format (Homebrew LLVM 22) and cppcheck 2.20 (the CI lint image) both clean.
  • Linux and Windows legs were not run locally: the installer change is string rendering plus an exact-match comparison, so PR CI is the gate.

Refs #715, #509

@DeusData
DeusData force-pushed the feat/codex-pretooluse-hook branch from f1614ab to d17aed2 Compare October 20, 2026 22:34
…se hook

Codex runs its hooks with the same contract as Claude Code: its shell tool is
named Bash, tool_input.command is a string, and PreToolUse hookSpecificOutput
additionalContext reaches the model (openai/codex hooks/src/events/
pre_tool_use.rs, core/src/hook_runtime.rs). Codex only had the SessionStart and
SubagentStart reminders (#330), so its grep and rg calls never got the graph
context Claude Code's searches get (#715).

- config.toml: the managed block adds [[hooks.PreToolUse]] matcher = "Bash"
  running the same hook-augment command. The previous block (lifecycle pair
  only, as installs up to v0.12 wrote it) stays recognized as owned, so an
  upgrade replaces it in place and uninstall removes either generation.
- Inline forms (#1432): Codex's inline rewrite of our PreToolUse is replaced.
  A user's own inline hooks.PreToolUse is kept and our block then leaves
  PreToolUse out, because TOML cannot hold an inline array and array-of-tables
  entries for the same key; install says so instead of failing.
- hooks.json: the Codex-only PreToolUse Bash entry is added and removed next to
  the lifecycle pair, touching only the entry whose command is ours.

Codex asks users to re-trust changed hook definitions (/hooks).

Refs #715, #509

Signed-off-by: Martin Vogel <[email protected]>
…oolUse block

config_toml_codex_accepts_v0102_managed_windows_crlf asserted that re-upserting
over the v0.10.2 lifecycle-only block left the file byte-identical. That block
is now the previous generation: the upsert accepts it as owned and upgrades it
in place. The test now pins that upgrade (one PreToolUse table, BOM, CRLF and
the surrounding tables kept) and that a second upsert is byte-identical.

Refs #715

Signed-off-by: Martin Vogel <[email protected]>
@DeusData
DeusData force-pushed the feat/codex-pretooluse-hook branch from d17aed2 to 18d7273 Compare October 25, 2026 16:57
@DeusData
DeusData merged commit 619192a into main Oct 25, 2026
35 checks passed
taggedzi pushed a commit to taggedzi/codebase-memory-mcp that referenced this pull request Oct 27, 2026
…h hooks

The Codex CLI row still listed only SessionStart and SubagentStart after the
PreToolUse Bash hook landed (DeusData#2719), and the hook section described only
Claude Code's search context. The row now names the Codex search hook, and the
section says how Codex, Copilot CLI and VS Code's Copilot runtime get search
context.

Refs DeusData#509

Signed-off-by: Martin Vogel <[email protected]>
DeusData added a commit that referenced this pull request Nov 4, 2026
Maintainer refresh of #1862 (persistent install-hook opt-out, direction
decided in #753) onto main a5b248f. The first refresh is 2577a86 and
carries the semantic fixes for shell-free Claude hooks.

Textual conflicts, all resolved by keeping both sides:

- src/cli/cli.h: main added CBM_CONFIG_DOC_CANDIDATES on the line where
  the PR added CBM_CONFIG_INSTALL_HOOKS. Both defines are kept, in
  alphabetical order.

- src/cli/cli.c CONFIG_KEYS[]: main added the watch_non_git row where the
  PR added install_hooks. Main row stays first, followed by the
  install_hooks row.

- src/cli/cli.c Codex hook install: main (#2719) declared pretool_written
  in front of the hooks.json branch the PR prefixed with its --no-hooks
  removal branch. The declaration stays first, then the PR removal
  branch, then main hooks.json and config.toml upsert branches unchanged.

Check: for every file the PR touches, the added and removed lines of the
merged tree against main equal those of the PR against its old base.

No compile-level semantic conflict: no function the PR calls changed its
signature, and the PR re-signs no function main calls. The new Codex
PreToolUse hook from #2719 is gated by the opt-out in the next commit.

Signed-off-by: Martin Vogel <[email protected]>
DeusData added a commit that referenced this pull request Nov 4, 2026
…-out

#2719 added a PreToolUse Bash hook for Codex after this opt-out was
written. In config.toml it lives inside the managed block, which the
opt-out already removes. In hooks.json it is a separate entry beside the
lifecycle pair, and the opt-out removed only the pair, so after
install --no-hooks, and on every later bare install or update that
respects the stored preference, our PreToolUse hook stayed registered.

The --no-hooks branch now also calls cbm_remove_codex_pretool_hooks_json,
the exact-command remover that uninstall uses since #2719. A PreToolUse
hook the user owns is left alone.

Test: cli_no_hooks_removes_codex_pretool_hook drives the real install
dispatch through both hook representations. A default install writes ours
beside a foreign hook; --no-hooks and a bare install with the stored
preference remove ours and keep the foreign hook and the MCP entry; and
--hooks restores ours. Without the fix it fails: ours is still in
hooks.json after --no-hooks.

Signed-off-by: Martin Vogel <[email protected]>
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