mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
211 lines
11 KiB
Cheetah
211 lines
11 KiB
Cheetah
## Step 2C: Consult Mode
|
|
|
|
Ask Codex anything about the codebase. Supports session continuity for follow-ups.
|
|
|
|
1. **Check for existing session:**
|
|
```bash
|
|
cat .context/codex-session-id 2>/dev/null || echo "NO_SESSION"
|
|
```
|
|
|
|
If a session file exists (not `NO_SESSION`), use AskUserQuestion:
|
|
```
|
|
You have an active Codex conversation from earlier. Continue it or start fresh?
|
|
A) Continue the conversation (Codex remembers the prior context)
|
|
B) Start a new conversation
|
|
```
|
|
|
|
2. Create temp files:
|
|
```bash
|
|
TMPRESP=$(mktemp "$TMP_ROOT/codex-resp-XXXXXX")
|
|
TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX")
|
|
```
|
|
|
|
3. **Plan review auto-detection:** If the user's prompt is about reviewing a plan,
|
|
or if plan files exist and the user said `/codex` with no arguments:
|
|
```bash
|
|
setopt +o nomatch 2>/dev/null || true # zsh compat
|
|
ls -t "$PLAN_ROOT"/*.md 2>/dev/null | xargs grep -l "$(basename $(pwd))" 2>/dev/null | head -1
|
|
```
|
|
If no project-scoped match, fall back to `ls -t "$PLAN_ROOT"/*.md 2>/dev/null | head -1`
|
|
but warn: "Note: this plan may be from a different project — verify before sending to Codex."
|
|
|
|
**IMPORTANT — embed content, don't reference path:** Codex runs sandboxed to the repo
|
|
root and cannot access `~/.claude/plans/` or any files outside the repo. You MUST
|
|
read the plan file yourself and embed its FULL CONTENT in the prompt below. Do NOT tell
|
|
Codex the file path or ask it to read the plan file — it will waste 10+ tool calls
|
|
searching and fail.
|
|
|
|
Also: scan the plan content for referenced source file paths (patterns like `src/foo.ts`,
|
|
`lib/bar.py`, paths containing `/` that exist in the repo). If found, list them in the
|
|
prompt so Codex reads them directly instead of discovering them via rg/find.
|
|
|
|
**Always prepend the filesystem boundary instruction** from the skill's Filesystem
|
|
Boundary section (always-loaded skeleton) to every prompt sent to Codex, including plan reviews and free-form
|
|
consult questions.
|
|
|
|
Prepend the boundary and persona to the user's prompt:
|
|
"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 modify agents/openai.yaml. Stay focused on repository code only.
|
|
|
|
You are a brutally honest technical reviewer. Review this plan for: logical gaps and
|
|
unstated assumptions, missing error handling or edge cases, overcomplexity (is there a
|
|
simpler approach?), feasibility risks (what could go wrong?), and missing dependencies
|
|
or sequencing issues. Be direct. Be terse. No compliments. Just the problems.
|
|
Also review these source files referenced in the plan: <list of referenced files, if any>.
|
|
|
|
THE PLAN:
|
|
<full plan content, embedded verbatim>"
|
|
|
|
For non-plan consult prompts (user typed `/codex <question>`), still prepend the boundary:
|
|
"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 modify agents/openai.yaml. Stay focused on repository code only.
|
|
|
|
<user's question>"
|
|
|
|
4. Run codex exec with **JSONL output** to capture reasoning traces. Use
|
|
`timeout: 660000` on the Bash call (for both new and resumed sessions) — the gate
|
|
sits ABOVE the 600s wrapper so the wrapper fires first with its explicit stall
|
|
message:
|
|
|
|
If the user passed `--xhigh`, use `"xhigh"` instead of `"medium"`.
|
|
|
|
For a **new session:**
|
|
```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: wrap with timeout (gtimeout/timeout fallback chain via probe helper)
|
|
_gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only {{CODEX_MODEL_CONFIG_FLAG}} -c 'model_reasoning_effort="medium"' {{CODEX_WEB_SEARCH_FLAG}} --json < /dev/null 2>"$TMPERR" | 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 == 'thread.started':
|
|
tid = obj.get('thread_id','')
|
|
if tid: print(f'SESSION_ID:{tid}', flush=True)
|
|
elif 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
|
|
# Three-way completeness check (#2671; consult previously had NONE): a STATED
|
|
# failure is a failure, not a network problem; only silence 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)
|
|
"
|
|
# Fix 1: hang detection for Consult new-session (mirrors Challenge + resume)
|
|
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
|
if [ "$_CODEX_EXIT" = "124" ]; then
|
|
_gstack_codex_log_event "codex_timeout" "600"
|
|
_gstack_codex_log_hang "consult" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
|
|
echo "Codex stalled past 10 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" "consult:$_CODEX_EXIT"
|
|
fi
|
|
```
|
|
|
|
**Session-cost reality (#2387, measured):** every `codex exec` call — resumed
|
|
or fresh — pays Codex's ~21K-token session prelude (its skill catalogue +
|
|
instructions); `resume` does NOT amortize it (a measured resume came in
|
|
slightly ABOVE a fresh call). Resume buys conversational continuity, never
|
|
token savings. So: prefer ONE codex call per skill where the workflow allows,
|
|
batch questions into that call, and reach for resume only when the follow-up
|
|
genuinely needs the prior session's context.
|
|
|
|
For a **resumed session** (user chose "Continue"):
|
|
```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
|
|
cd "$_REPO_ROOT" || exit 1
|
|
# Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper)
|
|
_gstack_codex_timeout_wrapper 600 codex exec resume <session-id> "<prompt>" -c 'sandbox_mode="read-only"' {{CODEX_MODEL_CONFIG_FLAG}} -c 'model_reasoning_effort="medium"' {{CODEX_WEB_SEARCH_FLAG}} --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c "
|
|
<same python streaming parser as above, with flush=True on all print() calls>
|
|
"
|
|
# Fix 1: same hang detection pattern as new-session block
|
|
_CODEX_EXIT=${PIPESTATUS[0]:-${pipestatus[1]}} # bash sets PIPESTATUS; zsh (lowercase, 1-indexed) falls through (#2669)
|
|
if [ "$_CODEX_EXIT" = "124" ]; then
|
|
_gstack_codex_log_event "codex_timeout" "600"
|
|
_gstack_codex_log_hang "consult-resume" "$(wc -c < "$TMPERR" 2>/dev/null || echo 0)"
|
|
echo "Codex stalled past 10 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" "consult-resume:$_CODEX_EXIT"
|
|
fi
|
|
```
|
|
|
|
5. Capture session ID from the streamed output. The parser prints `SESSION_ID:<id>`
|
|
from the `thread.started` event. Save it for follow-ups:
|
|
```bash
|
|
mkdir -p .context
|
|
```
|
|
Save the session ID printed by the parser (the line starting with `SESSION_ID:`)
|
|
to `.context/codex-session-id`.
|
|
|
|
6. Present the full streamed output:
|
|
|
|
```
|
|
CODEX SAYS (consult):
|
|
════════════════════════════════════════════════════════════
|
|
<full output, verbatim — includes [codex thinking] traces>
|
|
════════════════════════════════════════════════════════════
|
|
Tokens: N | Est. cost: ~$X.XX
|
|
Session saved — run /codex again to continue this conversation.
|
|
```
|
|
|
|
7. After presenting, note any points where Codex's analysis differs from your own
|
|
understanding. If there is a disagreement, flag it:
|
|
"Note: Claude Code disagrees on X because Y."
|
|
|
|
8. **Synthesis recommendation (REQUIRED).** Emit ONE recommendation line
|
|
summarizing what the user should do based on Codex's consult output, in the
|
|
canonical format the AskUserQuestion judge grades:
|
|
|
|
```
|
|
Recommendation: <action> because <one-line reason that names the most actionable insight from Codex>
|
|
```
|
|
|
|
Examples (the strongest reasons compare Codex's insight against an alternative — different recommendation, status-quo, or another Codex point):
|
|
- `Recommendation: Adopt Codex's sharding suggestion because it eliminates the head-of-line blocking the current writer-pool has, while the cache-layer alternative Codex also floated still has a single-writer hot path.`
|
|
- `Recommendation: Reject Codex's "use SQLite instead" suggestion because the team's Postgres operational experience outweighs the simplicity gain at the projected scale, and Codex's secondary suggestion (read replicas) handles the read-load concern that motivated the SQLite pivot.`
|
|
- `Recommendation: Investigate Codex's flagged migration ordering before D3 lands because it surfaces a real foreign-key cycle that the in-house schema review missed, while the styling concern Codex also raised can wait for a follow-up.`
|
|
|
|
The reason must engage with a specific Codex insight and compare against an alternative (a different recommendation, status-quo, or another Codex point). Generic synthesis ("because Codex raised good points") fails the format. **Never silently auto-decide; always emit the line.**
|
|
|
|
---
|