Skip to content

Multiple Claude Code security hook triggers in /codex and /autoplan skill templates #1329

@howie

Description

@howie

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions