fix(#1115): capability-probe codex hook-trust bypass flag in /gsd:review; fail loud on empty output (#1122)

On codex-cli < 0.137.0 the review.md `codex exec` invocation passed
--dangerously-bypass-hook-trust (added in 0.137) unconditionally and discarded
stderr, so codex exited "unexpected argument" before reading the prompt and the
empty output file was treated as a completed review — a silent degraded review.

- Capability-probe the flag (`codex exec --help | grep`) and apply it via
  $CODEX_BYPASS_FLAG only when supported (works fine without it on older CLIs).
- Capture codex stderr to a .err file instead of /dev/null, and replace an empty
  output with a diagnostic so a broken reviewer is surfaced (mirrors Cursor/OpenCode).
- Update enh-773 enforcement test to require the capability gate + fail-loud guard
  instead of the unconditional flag.

Closes #1115

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-12 15:55:17 -04:00
committed by GitHub
parent 1c073c1c81
commit fc37ae0c4f
4 changed files with 63 additions and 8 deletions

View File

@@ -15,10 +15,12 @@ describe('enh-773: automated codex exec invocations include --ephemeral and --da
'utf8'
);
// Extract all codex exec invocation lines from code fences
// Extract codex exec INVOCATION lines from code fences. The #1115 capability
// probe (`codex exec --help | grep …`) is not an automation invocation, so it
// is excluded from the per-invocation flag assertions below.
const codexExecLines = workflow
.split('\n')
.filter((line) => line.includes('codex exec'));
.filter((line) => line.includes('codex exec') && !line.includes('codex exec --help'));
test('review.md contains at least one codex exec invocation', () => {
assert.ok(
@@ -36,15 +38,46 @@ describe('enh-773: automated codex exec invocations include --ephemeral and --da
}
});
test('every codex exec invocation includes --dangerously-bypass-hook-trust', () => {
test('#1115: the hook-trust bypass is capability-gated, not passed unconditionally', () => {
// --dangerously-bypass-hook-trust only exists on codex-cli >= 0.137.0. It must
// be probed (`codex exec --help | grep`) and applied via $CODEX_BYPASS_FLAG so
// older installs do not fail with "unexpected argument" (a silent empty review).
assert.ok(
/codex exec --help[^\n]*grep[^\n]*--dangerously-bypass-hook-trust/.test(workflow),
'review.md must capability-probe --dangerously-bypass-hook-trust via `codex exec --help | grep`'
);
assert.ok(
workflow.includes('CODEX_BYPASS_FLAG="--dangerously-bypass-hook-trust"'),
'the probe must set CODEX_BYPASS_FLAG to the flag when the CLI supports it'
);
for (const line of codexExecLines) {
assert.ok(
line.includes('--dangerously-bypass-hook-trust'),
`codex exec invocation is missing --dangerously-bypass-hook-trust:\n ${line.trim()}`
line.includes('$CODEX_BYPASS_FLAG'),
`codex exec invocation must apply the capability-gated $CODEX_BYPASS_FLAG, not an unconditional flag:\n ${line.trim()}`
);
// …and must NOT also pass the literal flag (that would reintroduce #1115).
assert.ok(
!line.includes('--dangerously-bypass-hook-trust'),
`codex exec invocation must not pass the literal --dangerously-bypass-hook-trust (use the gated $CODEX_BYPASS_FLAG):\n ${line.trim()}`
);
}
});
test('#1115: codex review failures are surfaced, not silently swallowed', () => {
// stderr must be captured (not discarded to /dev/null) and an empty output
// must be replaced with a diagnostic, so a broken reviewer is reported.
for (const line of codexExecLines) {
assert.ok(
!line.includes('2>/dev/null'),
`codex exec must not discard stderr to /dev/null:\n ${line.trim()}`
);
}
assert.ok(
/\[ ! -s \/tmp\/gsd-review-codex-\{phase\}\.md \]/.test(workflow),
'review.md must guard against an empty codex review output and surface the failure'
);
});
test('--ephemeral appears before the prompt argument (flag ordering)', () => {
for (const line of codexExecLines) {
const ephemeralPos = line.indexOf('--ephemeral');

View File

@@ -62,7 +62,7 @@
"remove-phase.md": 8469,
"remove-workspace.md": 7507,
"resume-project.md": 15288,
"review.md": 37079,
"review.md": 38031,
"scan.md": 7688,
"secure-phase.md": 12187,
"session-report.md": 4044,