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)
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)
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)
_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 <path> to set the working directory; the cd is unnecessary for those modes.
Pattern 4 — inline python3 -u -c "..." multi-line blocks (Challenge / Consult modes)
... | 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:
# 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)
# 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 <key> 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 <path>. For codex review, use a subshell to avoid CWD pollution:
# 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)
# 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
Problem
Running
/codexor/autoplaninside 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)Hook message:
'source' evaluates arguments as shell codeClaude Code flags all
sourcebuiltins 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)Hook message:
Unhandled node type: stringClaude Code's static bash AST parser cannot handle tilde-path inside
$()inside a double-quotedevalargument — the resultingstringnode is abandoned.gstack-pathsexplicitly documentseval "$(gstack-paths)"as its intended calling convention in the file header.Pattern 3 —
cd "$_REPO_ROOT"aftergit rev-parse(Review / Challenge / Consult modes)Hook message: C-class (
changes directory before running git) or no message (silent CWD pollution).codex execalready accepts-C <path>to set the working directory; thecdis unnecessary for those modes.Pattern 4 — inline
python3 -u -c "..."multi-line blocks (Challenge / Consult modes)Two hooks fire simultaneously:
Newline followed by # inside a quoted argument can hide arguments from path validation— the# Fix N:inline Python comments trigger B-class injection detectionThis is the highest-impact pattern because it sits in the main execution paths for Challenge and Consult modes.
Impact
A single
/codex reviewrun 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-probefunctions to standalone binaries (eliminates Pattern 1)Each
_gstack_codex_*function becomes a direct binary call, removing the need forsourceentirely:Note:
gstack-codex-log-eventcurrently reads$_TELset by the caller. As a standalone binary it should callgstack-config get telemetryinternally or accept a--telemetry on/offflag.Fix 2 — Replace
eval "$(gstack-paths)"with individual--getcalls (eliminates Pattern 2)Requires adding
--get <key>support togstack-paths; existing bare invocation should remain backward-compatible.Fix 3 — Replace
cd "$_REPO_ROOT"with subshell or-Cflag (eliminates Pattern 3)codex execalready accepts-C <path>. Forcodex review, use a subshell to avoid CWD pollution:Fix 4 — Extract inline Python JSONL parser to a standalone script (eliminates Pattern 4, highest priority)
This is the highest-priority fix: it removes both the AP1 complexity violation and the B-class
# commentinjection detection in one change, and affects the most-used flows (Challenge and Consult).Environment
~/.claude/skills/gstack/