garrytan / garrytan/gstack

/plan-eng-review and /plan-ceo-review: the Codex outside voice is killed by a hard-coded 300 s Bash timeout (no wrapper, no config knob), then silently replaced by a same-model fallback

Open
#2,776 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133k
Forks
19.9k
Avg merge
18h 46m
Merged PRs (30d)
26

Description

## Summary

`/plan-eng-review` and `/plan-ceo-review` run their Codex outside voice under a hard-coded 5-minute Bash timeout (`timeout: 300000`) with no inner `timeout` wrapper and no config knob. On a large plan the cap fires while Codex is still reading, the harness kills the process (exit -1, "process exited while detached; exit code unknown"), stdout is empty, and the skill silently falls back to a Claude subagent. The user asked for an outside model and gets the same model family, with nothing but a one-line notice.

The cap is the shortest Codex bound in the suite, and the only one without the `_gstack_codex_timeout_wrapper`:

| Skill | Inner wrapper | Bash gate | Source |
|---|---|---|---|
| `/plan-eng-review` outside voice | none | 300 s | `plan-eng-review/sections/review-sections.md:422` |
| `/plan-ceo-review` outside voice | none | 300 s | `plan-ceo-review/sections/review-sections.md:341` |
| `/review` adversarial (codex exec / codex review) | 540 s | 600 s | `review/sections/adversarial.md:99-102, 132-137` |
| `/codex review` | 330 s | 360 s | `codex/sections/review-mode.md:107` |
| `/codex challenge`, `/codex consult` | 600 s | 660 s | `codex/sections/challenge-mode.md:23`, `consult-mode.md:66` |

## What happened

gstack 1.79.0.0, codex-cli 0.152.0, model `gpt-5.6-sol`, `model_reasoning_effort="high"`, `web_search="cached"`, `-s read-only`.

The plan under review is a 2,945-line, ~250 KB design doc. The skill's own prompt construction truncates the plan to 30 KB and, because Codex runs with `-C -s read-only`, the prompt told it the file path and that it could read the rest itself. Codex did exactly that: in 300 s it made 15 `exec` calls (sed/grep over the design and the requirements docs), wrote 9,348 lines to stderr, and had not started composing an answer when the Bash timeout killed it.

```
$ timeout 300 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' -c 'web_search="cached"' < /dev/null
EXIT: -1 # harness kill, not codex's own exit
stdout: 0 bytes
stderr: 9348 lines, last of them grep output from the repo
```

The skill then branched to "Empty response / Timeout: fall back to the Claude subagent" and the review continued with a same-family second opinion. Its findings were useful, but the "Outside Voice" row in the review report now records a Claude source for a run the user configured Codex for.

This is not a one-off on this repo: the `/review` adversarial pass has timed out at its 540 s wrapper (exit 124) on two earlier runs here as well, so even the suite's longer caps are marginal for a plan this size with reasoning effort high.

## Why the cap is the wrong shape

1. **It is not the user's call.** Nothing in `gstack-config` sets it; the number lives in a generated skill section. A plan review is the one place where waiting ten minutes for a genuinely different model is the whole point, and the user cannot opt into that wait.
2. **It kills instead of interrupting.** Without the `_gstack_codex_timeout_wrapper` the Bash gate is the only bound, so the run ends as a harness kill with no exit code rather than a clean 124 the skill can distinguish from "codex crashed". The `/codex` and `/review` skills already solved this with wrapper-below-gate; the plan reviews did not get the same treatment.
3. **The fallback hides the downgrade.** A timeout is treated as "informational" and the review proceeds. The review report's Outside Voice row then reads as if a second opinion ran, and only the `source: claude` field says it was the same model family.
4. **The prompt invites the exploration the cap cannot afford.** "Plan truncated for size; read the file for the rest" plus read-only repo access is a reasonable instruction with a 20-minute budget and a trap with a 5-minute one.

## Suggested fix

- Make the bound configurable (`gstack-config set codex_plan_review_timeout_seconds N`, or one `codex_timeout_seconds` for the suite) with a default well above 300 s for the plan reviews; `/codex challenge`'s 600 s is a reasonable floor given it is the same kind of task.
- Run the plan-review Codex call through `_gstack_codex_timeout_wrapper ` with the Bash gate above it, as `/review` and `/codex` already do, so a timeout is a clean exit 124 with the skill's hang-detection logging.
- On timeout, ask before falling back (interactive sessions) or at least record `outside_voice: fallback` visibly in the review report, so a same-model second opinion is never mistaken for a cross-model one.
- Optionally, when the plan exceeds the 30 KB truncation, send the sections the reviewer must judge (implementation tasks, data model, failure modes) rather than the first 30 KB plus an invitation to read.

Happy to test a fix on the same plan.

Contributor guide

Open the contributing guide

Research direction

Start with plan-eng-review/sections/review-sections.md:422 and plan-ceo-review/sections/review-sections.md:341, then compare the timeout wrapper usage in review/sections/adversarial.md and codex/sections/review-mode.md. Trace the configuration and fallback/reporting paths. Done means the plan-review timeout is configurable, uses the wrapper with a clean timeout result, and visibly identifies any fallback source.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, typescript
Domain
cli, developer-experience, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.