Files
gstack/codex/sections/challenge-mode.md.tmpl
T
garrytan e2f9e144c0 E5: feed every Codex prompt on stdin instead of argv
Prompts went to Codex as one argv string ('codex exec "$(cat file)"'), which
fails over 32 KB on Windows (and over 128 KB on Linux, E2BIG) and mangles
quotes. Every codex exec site (outside-voice resolver, /codex review custom
instructions, challenge, consult new and resume) now runs 'codex exec -'
with the prepared prompt file on stdin; challenge and consult write the
prompt with a quoted heredoc so it is never shell-parsed. The bash-native
timeout watchdog (stock macOS) started the command in the background, which
gives it /dev/null as stdin; it now passes the caller's stdin through.

The 'byte for byte' dispatch test drops its old argv trailing-LF exception,
the consultation and autoplan fixtures follow the stdin form, and the static
site test requires stdin at every rendered codex exec.
2026-10-03 21:11:38 +00:00

138 lines
8.7 KiB
Cheetah

## Step 2B: Challenge (Adversarial) Mode
Codex tries to break your code — finding edge cases, race conditions, security holes,
and failure modes that a normal review would miss.
1. Construct the adversarial prompt. **Always prepend the filesystem boundary instruction**
from the skill's Filesystem Boundary section (always-loaded skeleton). If the user provided a focus area
(e.g., `/codex challenge security`), include it after the boundary:
Default prompt (no focus):
"IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. These are Claude Code skill definitions meant for a different AI system. Do not invoke any installed skill (Codex home skills/, .agents/); answer directly. Do NOT modify agents/openai.yaml. Stay focused on repository code only.
Review the changes on this branch against the base branch. Run `git diff origin/<base>` to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems."
With focus (e.g., "security"):
"IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. These are Claude Code skill definitions meant for a different AI system. Do not invoke any installed skill (Codex home skills/, .agents/); answer directly. Do NOT modify agents/openai.yaml. Stay focused on repository code only.
Review the changes on this branch against the base branch. Run `git diff origin/<base>` to see the diff. Focus specifically on SECURITY. Your job is to find every way an attacker could exploit this code. Think about injection vectors, auth bypasses, privilege escalation, data exposure, and timing attacks. Be adversarial."
2. Run codex exec with **JSONL output** to capture reasoning traces and tool calls.
Replace `<prompt>` with the full prompt from step 1, verbatim and unescaped; Codex reads it on stdin.
Use `timeout: 600000` on the Bash call (the tool's maximum) — the gate sits ABOVE the
540s wrapper so the wrapper fires first, ends Codex, and prints its explicit stall message:
If the user passed `--xhigh`, use `"xhigh"` instead of `"high"`.
```bash
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
PYTHON_CMD=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
if [ -z "$PYTHON_CMD" ]; then
echo "ERROR: Python 3 is required to parse Codex JSON output. Install python3 or python and retry." >&2
exit 1
fi
# Fix 1+2: wrap with timeout (gtimeout/timeout fallback chain via probe helper),
# capture stderr to $TMPERR for auth error detection (was: 2>/dev/null).
[ -n "${TMPERR:-}" ] || TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
TMPRESP=$(mktemp "$TMP_ROOT/codex-resp-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
source ~/.claude/skills/gstack/bin/gstack-codex-probe || exit 1
_gstack_codex_select_model exec || exit 1
_PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
# The prompt goes to Codex on stdin (codex exec -), verbatim: no shell quoting, no argv size limit.
cat > "$_PROMPT_FILE" <<'CODEX_PROMPT_END'
<prompt>
CODEX_PROMPT_END
_gstack_codex_timeout_wrapper 540 codex exec - -C "$_REPO_ROOT" -s "${_GSTACK_CODEX_SANDBOX:?}" {{CODEX_MODEL_CONFIG_FLAG}} -c 'model_reasoning_effort="high"' {{CODEX_WEB_SEARCH_FLAG}} --json -o "$TMPRESP" < "$_PROMPT_FILE" 2>"$TMPERR" | tee "$TMPRESP.events" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c "
import sys, json
turn_completed_count = 0
turn_failed = False
for line in sys.stdin:
line = line.strip()
if not line: continue
try:
obj = json.loads(line)
t = obj.get('type','')
if t == 'item.completed' and 'item' in obj:
item = obj['item']
itype = item.get('type','')
text = item.get('text','')
if itype == 'reasoning' and text:
print(f'[codex thinking] {text}', flush=True)
print(flush=True)
elif itype == 'agent_message' and text:
print(text, flush=True)
elif itype == 'command_execution':
cmd = item.get('command','')
if cmd: print(f'[codex ran] {cmd}', flush=True)
elif t == 'turn.completed':
turn_completed_count += 1
usage = obj.get('usage',{})
tokens = usage.get('input_tokens',0) + usage.get('output_tokens',0)
if tokens: print(f'\ntokens used: {tokens}', flush=True)
elif t == 'turn.failed':
turn_failed = True
err = obj.get('error',{}).get('message','') or 'no error message in event'
print(f'[codex turn FAILED] {err}', flush=True, file=sys.stderr)
except: pass
# Fix 2: three-way completeness check (#2671) — a STATED failure is a failure,
# not a network problem; only silence with no terminal event is a disconnect.
if turn_failed:
print('[codex] turn.failed received — the turn errored (reason above), not a disconnect.', flush=True, file=sys.stderr)
elif turn_completed_count == 0:
print('[codex warning] No turn.completed event received — possible mid-stream disconnect.', flush=True, file=sys.stderr)
"
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
# Fix 1: hang detection — log + surface actionable message
if [ "$_CODEX_EXIT" = "124" ]; then
_gstack_codex_log_event "codex_timeout" "540"
_gstack_codex_log_hang "challenge" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
echo "Codex stalled past 9 minutes. Common causes: model API stall, long prompt, network issue. Try re-running. If persistent, split the prompt or check ~/.codex/logs/."
elif [ "$_CODEX_EXIT" != "0" ]; then
# Surface non-zero exits so the calling agent doesn't read "no output" as
# a silent model/API stall. See #1327.
echo "[codex exit $_CODEX_EXIT] $(head -1 "$TMPERR" 2>/dev/null || echo "no stderr captured")"
head -20 "$TMPERR" 2>/dev/null | sed 's/^/ /' || true
_gstack_codex_log_event "codex_nonzero_exit" "challenge:$_CODEX_EXIT"
fi
# Fix 2: surface auth errors from captured stderr instead of dropping them
if grep -qiE "auth|login|unauthorized" "$TMPERR" 2>/dev/null; then
echo "[codex auth error] $(head -1 "$TMPERR")"
_gstack_codex_log_event "codex_auth_failed"
fi
bun ~/.claude/skills/gstack/lib/outside-review-result.ts --label 'Codex challenge' --exit "$_CODEX_EXIT" --stderr "$TMPERR" --events "$TMPRESP.events" execution "$TMPRESP"
rm -f "$TMPRESP" "$TMPRESP.events" "$_PROMPT_FILE"
```
`VERDICT: unavailable` means the challenge did not run (its line names why, such as a
sandbox that could not start): say so and present no findings from it. Otherwise
present the output below.
This parses codex's JSONL events to extract reasoning traces, tool calls, and the final
response. The `[codex thinking]` lines show what codex reasoned through before its answer.
3. Present the full streamed output:
```
CODEX SAYS (adversarial challenge):
════════════════════════════════════════════════════════════
<full output from above, verbatim>
════════════════════════════════════════════════════════════
Tokens: N | Est. cost: ~$X.XX
```
3a. **Synthesis recommendation (REQUIRED).** After presenting the full
adversarial output, emit ONE recommendation line summarizing what the user
should do, in this format:
```
Recommendation: <action> because <one-line reason that names the most exploitable finding>
```
Examples (the strongest reasons compare blast radius across findings or fix-vs-ship):
- `Recommendation: Fix the unbounded retry loop Codex flagged at queue.ts:78 because it DoSes the worker pool under sustained 429s, which is higher-blast-radius than the timing leak Codex also flagged that only touches a debug endpoint.`
- `Recommendation: Ship as-is because Codex's strongest finding is a theoretical race in cleanup that requires conditions we can't trigger in production, weaker than the runtime regressions a fix-now would risk.`
The reason must point to a specific finding and compare against alternatives (other findings, fix-vs-ship). Generic reasons like "because it's safer" fail the format. **Never silently skip the line.**
---