From ff00b11befda9e98318550b9dd81880723c1a6e7 Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Sat, 4 Apr 2026 02:07:29 +0200 Subject: [PATCH 1/5] feat: add PreToolUse hook to block direct codex CLI calls Adds a Bash PreToolUse hook that intercepts bare "codex" CLI invocations and redirects Claude to use the plugin commands instead. The plugin's own calls via codex-companion.mjs are allowlisted. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/hooks/block-direct-codex-cli.sh | 16 ++++++++++++++++ plugins/codex/hooks/hooks.json | 12 ++++++++++++ 2 files changed, 28 insertions(+) create mode 100755 plugins/codex/hooks/block-direct-codex-cli.sh diff --git a/plugins/codex/hooks/block-direct-codex-cli.sh b/plugins/codex/hooks/block-direct-codex-cli.sh new file mode 100755 index 0000000..839487a --- /dev/null +++ b/plugins/codex/hooks/block-direct-codex-cli.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# PreToolUse hook: block direct "codex" CLI invocations and redirect to the plugin. +INPUT=$(cat) +COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') + +# Match bare "codex" at the start of the command or after a pipe/semicolon/&& +if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)codex\s'; then + # Allow calls that go through the plugin's own companion script + if echo "$COMMAND" | grep -q 'codex-companion\.mjs'; then + exit 0 + fi + echo "Do not call the codex CLI directly. Use the codex plugin instead: /codex:rescue for tasks, /codex:review for reviews, /codex:status for status, /codex:result for results." >&2 + exit 2 +fi + +exit 0 diff --git a/plugins/codex/hooks/hooks.json b/plugins/codex/hooks/hooks.json index 19e33b8..3fe4daf 100644 --- a/plugins/codex/hooks/hooks.json +++ b/plugins/codex/hooks/hooks.json @@ -1,6 +1,18 @@ { "description": "Optional stop-time review gate for Codex Companion.", "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/block-direct-codex-cli.sh\"", + "timeout": 5 + } + ] + } + ], "SessionStart": [ { "hooks": [ From d91036615900d4f96c4abbb34a931873b2e585bc Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Sat, 4 Apr 2026 02:19:15 +0200 Subject: [PATCH 2/5] fix: remove unnecessary companion allowlist and add jq availability check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regex `codex\s` already won't match `codex-companion` (hyphen, not space), so the substring allowlist was redundant and could be bypassed with chained commands. Removed it. Also added a jq availability check that passes through gracefully if jq is missing — no hard dependency introduced but the guard works when jq is present (which we expect). Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/hooks/block-direct-codex-cli.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/codex/hooks/block-direct-codex-cli.sh b/plugins/codex/hooks/block-direct-codex-cli.sh index 839487a..9919b5c 100755 --- a/plugins/codex/hooks/block-direct-codex-cli.sh +++ b/plugins/codex/hooks/block-direct-codex-cli.sh @@ -1,14 +1,15 @@ #!/bin/bash # PreToolUse hook: block direct "codex" CLI invocations and redirect to the plugin. + +# jq is expected but not a hard dependency — pass through if unavailable. +command -v jq &>/dev/null || exit 0 + INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') # Match bare "codex" at the start of the command or after a pipe/semicolon/&& +# "codex-companion" won't match because \s requires whitespace after "codex". if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)codex\s'; then - # Allow calls that go through the plugin's own companion script - if echo "$COMMAND" | grep -q 'codex-companion\.mjs'; then - exit 0 - fi echo "Do not call the codex CLI directly. Use the codex plugin instead: /codex:rescue for tasks, /codex:review for reviews, /codex:status for status, /codex:result for results." >&2 exit 2 fi From 9fe289e30c466f14b5b580f5df372f22046ddef5 Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Sat, 4 Apr 2026 21:10:15 +0200 Subject: [PATCH 3/5] fix: use POSIX [[:space:]] instead of \s in grep pattern for BSD/macOS portability Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/hooks/block-direct-codex-cli.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/codex/hooks/block-direct-codex-cli.sh b/plugins/codex/hooks/block-direct-codex-cli.sh index 9919b5c..3d9172d 100755 --- a/plugins/codex/hooks/block-direct-codex-cli.sh +++ b/plugins/codex/hooks/block-direct-codex-cli.sh @@ -8,8 +8,8 @@ INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') # Match bare "codex" at the start of the command or after a pipe/semicolon/&& -# "codex-companion" won't match because \s requires whitespace after "codex". -if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)codex\s'; then +# "codex-companion" won't match because [[:space:]] requires whitespace after "codex". +if echo "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)codex[[:space:]]'; then echo "Do not call the codex CLI directly. Use the codex plugin instead: /codex:rescue for tasks, /codex:review for reviews, /codex:status for status, /codex:result for results." >&2 exit 2 fi From 10b6e726b74c704ce34354619af41e3ac725d5af Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Sat, 4 Apr 2026 21:11:02 +0200 Subject: [PATCH 4/5] fix: also block bare "codex" with no arguments (end-of-string match) Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/hooks/block-direct-codex-cli.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/codex/hooks/block-direct-codex-cli.sh b/plugins/codex/hooks/block-direct-codex-cli.sh index 3d9172d..62677f8 100755 --- a/plugins/codex/hooks/block-direct-codex-cli.sh +++ b/plugins/codex/hooks/block-direct-codex-cli.sh @@ -8,8 +8,9 @@ INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') # Match bare "codex" at the start of the command or after a pipe/semicolon/&& -# "codex-companion" won't match because [[:space:]] requires whitespace after "codex". -if echo "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)codex[[:space:]]'; then +# Also matches bare "codex" with no arguments (end of string). +# "codex-companion" won't match because the word boundary requires whitespace or EOL after "codex". +if echo "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)codex([[:space:]]|$)'; then echo "Do not call the codex CLI directly. Use the codex plugin instead: /codex:rescue for tasks, /codex:review for reviews, /codex:status for status, /codex:result for results." >&2 exit 2 fi From 55ac9fd6f42ae24e9ec3e201ef137133b96612fc Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Sun, 5 Apr 2026 18:16:33 +0200 Subject: [PATCH 5/5] fix: use positional matching to avoid false positives in arguments Previous broad word-boundary regex matched "codex" inside commit messages and string arguments. Reverts to positional approach (start of command or after shell separator) with added leading-whitespace support. Accepts the unlikely FOO=1 edge case as a trade-off. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/hooks/block-direct-codex-cli.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/codex/hooks/block-direct-codex-cli.sh b/plugins/codex/hooks/block-direct-codex-cli.sh index 62677f8..a0b13d3 100755 --- a/plugins/codex/hooks/block-direct-codex-cli.sh +++ b/plugins/codex/hooks/block-direct-codex-cli.sh @@ -7,10 +7,10 @@ command -v jq &>/dev/null || exit 0 INPUT=$(cat) COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') -# Match bare "codex" at the start of the command or after a pipe/semicolon/&& -# Also matches bare "codex" with no arguments (end of string). -# "codex-companion" won't match because the word boundary requires whitespace or EOL after "codex". -if echo "$COMMAND" | grep -qE '(^|[;&|][[:space:]]*)codex([[:space:]]|$)'; then +# Match "codex" at a command position: start of string (with optional leading +# whitespace) or after a shell separator. This avoids false positives when "codex" +# appears inside arguments like commit messages or strings. +if echo "$COMMAND" | grep -qE '(^[[:space:]]*|[;&|][[:space:]]*)codex([[:space:]]|$)'; then echo "Do not call the codex CLI directly. Use the codex plugin instead: /codex:rescue for tasks, /codex:review for reviews, /codex:status for status, /codex:result for results." >&2 exit 2 fi