fix(review,ship): run the codex diff passes under the timeout wrapper (#1036)

The `_gstack_codex_timeout_wrapper` added in #1056 was wired into
codex/SKILL.md but never into the /review and /ship diff passes, which kept
running under a bare 5-minute Bash gate. An unwrapped stall returns no exit
code and no output, which downstream reads as "Codex reviewed and found
nothing" — a truncated pass silently became a clean bill. Measured on
codex-cli 0.145.0: a pass was killed at 287s of a 300s budget mid-tool-call,
and the same prompt completed in 336s.

Both passes in scripts/resolvers/review.ts (adversarial `codex exec` and the
structured `codex review --base` pass) now re-source gstack-codex-probe and
run under `_gstack_codex_timeout_wrapper 540`, with the Bash tool gate raised
to 600000 ms so the wrapper fires FIRST and a stall surfaces as a diagnosable
exit 124. The timeout guidance now says a timed-out pass is MISSING COVERAGE,
not a clean result, and points at the run's rollout log under
~/.codex/sessions/ for partial output. The stale "timeout doesn't exist on
macOS" claim is gone — the wrapper resolves gtimeout, then timeout, then runs
unwrapped, so it is safe without coreutils.

Static guards in test/codex-hardening.test.ts pin all three sites (resolver,
review/SKILL.md, ship/sections/adversarial.md): both calls wrapped, wrapper
budget strictly under the Bash gate, and no reappearance of the macOS claim
that steered these call sites away from the wrapper in the first place. The
Claude-output path guard in test/gen-skill-docs.test.ts now scrubs
~/.codex/sessions/ (a user-facing Codex CLI path, same class as the
~/.codex/logs/ exemption) before banning Codex host paths. Generated files
regenerated via gen:skill-docs; factory golden refreshed.

Contributed by @aegixx (PR #2379).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 20:20:55 -07:00
co-authored by Claude Fable 5
parent 8c5bb4545b
commit fad28d81ce
6 changed files with 112 additions and 25 deletions
+48
View File
@@ -427,3 +427,51 @@ describe('codex SKILL.md.tmpl Step 2A: PROMPT + --base mutual exclusion guard',
});
}
});
// Regression guard for #1036. The wrapper added in #1056 was wired into
// codex/SKILL.md but not into the /review and /ship diff passes, which kept
// running under a bare 5-minute Bash gate. Measured on codex-cli 0.145.0: a
// pass was killed at 287s of a 300s budget mid-tool-call, and the same prompt
// completed in 336s. An unwrapped stall returns no exit code and no output,
// which downstream reads as "Codex reviewed and found nothing".
describe('codex timeout wrapper: /review + /ship diff passes', () => {
const WRAPPED_SITES = [
'scripts/resolvers/review.ts', // generator (source of truth)
'review/SKILL.md', // generated
'ship/sections/adversarial.md', // ship section source
];
// Outer Bash gate for the wrapped passes. The wrapper must be strictly
// shorter so IT fires first and the failure is a diagnosable exit 124.
const BASH_GATE_MS = 600000;
for (const relPath of WRAPPED_SITES) {
const read = () => fs.readFileSync(path.join(ROOT, relPath), 'utf8');
test(`${relPath}: both diff-review Codex calls run under the wrapper`, () => {
const wrapped =
read().match(/_gstack_codex_timeout_wrapper\s+\d+\s+codex\s+(exec|review)\b/g) ?? [];
// Adversarial pass + structured review pass.
expect(wrapped.length).toBeGreaterThanOrEqual(2);
});
test(`${relPath}: does not claim \`timeout\` is unavailable on macOS`, () => {
// _gstack_codex_timeout_wrapper resolves gtimeout -> timeout -> unwrapped,
// so the coreutils-less case is already handled. The old claim is what
// steered these call sites away from the wrapper in the first place.
expect(read()).not.toMatch(/doesn't exist on macOS/);
});
test(`${relPath}: wrapper budget stays under the outer Bash gate`, () => {
const budgets = [...read().matchAll(/_gstack_codex_timeout_wrapper\s+(\d+)\s+codex\b/g)].map(
(m) => Number(m[1]) * 1000,
);
expect(budgets.length).toBeGreaterThan(0);
for (const ms of budgets) {
// Inverting this makes the wrapper unreachable: the harness kills the
// call first and the exit-124 branch below it becomes dead code.
expect(ms).toBeLessThan(BASH_GATE_MS);
}
});
}
});