Multiple Claude Code security hook triggers in `/codex` and `/autoplan` skill templates
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Problem
Running `/codex` or `/autoplan` inside a Claude Code agent session triggers multiple built-in PreToolUse security hooks across different steps of the skill flow, causing repeated user confirmation prompts that break automated execution.
## Affected patterns
### Pattern 1 — `source gstack-codex-probe` (Step 0 and 0.5)
```bash
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null
```
**Hook message**: `'source' evaluates arguments as shell code`
Claude Code flags all `source` builtins unconditionally. `gstack-codex-probe`'s own header documents the intended usage ("Sourced from template bash blocks; never execute directly"), but the hook has no way to know the file is a zero-side-effect function library.
### Pattern 2 — `eval "$(gstack-paths)"` (Step 0.6)
```bash
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
```
**Hook message**: `Unhandled node type: string`
Claude Code's static bash AST parser cannot handle tilde-path inside `$()` inside a double-quoted `eval` argument — the resulting `string` node is abandoned. `gstack-paths` explicitly documents `eval "$(gstack-paths)"` as its intended calling convention in the file header.
### Pattern 3 — `cd "$_REPO_ROOT"` after `git rev-parse` (Review / Challenge / Consult modes)
```bash
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
cd "$_REPO_ROOT"
_gstack_codex_timeout_wrapper 330 codex review ...
```
**Hook message**: C-class (`changes directory before running git`) or no message (silent CWD pollution). `codex exec` already accepts `-C ` to set the working directory; the `cd` is unnecessary for those modes.
### Pattern 4 — inline `python3 -u -c "..."` multi-line blocks (Challenge / Consult modes)
```bash
... | PYTHONUNBUFFERED=1 python3 -u -c "
import sys, json
...
# Fix 2: completeness check — warn if no turn.completed received
if turn_completed_count == 0:
...
"
```
**Two hooks fire simultaneously**:
- AP1 complexity (multi-line + embedded language + nested quotes = score 3/5)
- **Hook message**: `Newline followed by # inside a quoted argument can hide arguments from path validation` — the `# Fix N:` inline Python comments trigger B-class injection detection
This is the highest-impact pattern because it sits in the main execution paths for Challenge and Consult modes.
## Impact
A single `/codex review` run can require **4 manual confirmations** before Codex actually starts. Challenge and Consult modes are similarly affected. This defeats automated execution.
## Suggested fixes
### Fix 1 — Convert `gstack-codex-probe` functions to standalone binaries (eliminates Pattern 1)
Each `_gstack_codex_*` function becomes a direct binary call, removing the need for `source` entirely:
```bash
# Before
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null
if ! _gstack_codex_auth_probe >/dev/null; then
_gstack_codex_log_event "codex_auth_failed"
_gstack_codex_timeout_wrapper 330 codex review ...
# After
if ! ~/.claude/skills/gstack/bin/gstack-codex-auth-probe >/dev/null; then
~/.claude/skills/gstack/bin/gstack-codex-log-event "codex_auth_failed"
~/.claude/skills/gstack/bin/gstack-codex-timeout 330 codex review ...
```
Note: `gstack-codex-log-event` currently reads `$_TEL` set by the caller. As a standalone binary it should call `gstack-config get telemetry` internally or accept a `--telemetry on/off` flag.
### Fix 2 — Replace `eval "$(gstack-paths)"` with individual `--get` calls (eliminates Pattern 2)
```bash
# Before
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
# After — 3 separate bash calls, no eval
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get state-root)
PLAN_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get plan-root)
TMP_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get tmp-root)
```
Requires adding `--get ` support to `gstack-paths`; existing bare invocation should remain backward-compatible.
### Fix 3 — Replace `cd "$_REPO_ROOT"` with subshell or `-C` flag (eliminates Pattern 3)
`codex exec` already accepts `-C `. For `codex review`, use a subshell to avoid CWD pollution:
```bash
# Before
cd "$_REPO_ROOT"
codex review ...
# After (subshell — does not pollute session CWD)
( cd "$_REPO_ROOT" && codex review ... )
```
### Fix 4 — Extract inline Python JSONL parser to a standalone script (eliminates Pattern 4, highest priority)
```bash
# Before — inline multi-line Python with # comments triggers two hooks
... | python3 -u -c "
import sys, json
# Fix 2: completeness check
...
"
# After — extract to bin/gstack-codex-jsonl-parser
... | ~/.claude/skills/gstack/bin/gstack-codex-jsonl-parser
```
This is the highest-priority fix: it removes both the AP1 complexity violation and the B-class `# comment` injection detection in one change, and affects the most-used flows (Challenge and Consult).
## Environment
- Claude Code (latest, 2026-05-05)
- gstack global install: `~/.claude/skills/gstack/`
- macOS
Contributor guide
Assessment
This issue has not been assessed yet.