Skip to content

Commit 9bae41c

Browse files
committed
fix: avoid extra codex health prompts
1 parent a3edbd7 commit 9bae41c

10 files changed

Lines changed: 74 additions & 39 deletions

File tree

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,6 @@ jobs:
134134
chmod 600 ~/.codex/config.toml
135135
fi
136136
137-
- name: Verify Codex OAuth headless mode
138-
env:
139-
CODEX_MODEL: ${{ vars.REVIEW_CODEX_MODEL }}
140-
run: |
141-
codex exec --model "$CODEX_MODEL" --sandbox read-only --ephemeral --ignore-user-config -c approval_policy=never -c model_reasoning_effort='"low"' --output-last-message /tmp/codex-smoke.txt "Respond with exactly: codex-oauth-ok"
142-
grep -q "codex-oauth-ok" /tmp/codex-smoke.txt
143-
144137
- name: Run ReviewRouter
145138
uses: 777genius/review-router@v1
146139
env:
@@ -150,6 +143,7 @@ jobs:
150143
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
151144
CODEX_MODEL: ${{ vars.REVIEW_CODEX_MODEL }}
152145
CODEX_REASONING_EFFORT: 'medium'
146+
CODEX_HEALTHCHECK_MODE: 'binary'
153147
CODEX_AGENTIC_CONTEXT: 'true'
154148
FAIL_ON_NO_HEALTHY_PROVIDERS: 'true'
155149
INLINE_MAX_COMMENTS: '5'

__tests__/e2e/install-script.e2e.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,15 @@ describe('review-router curl installer e2e', () => {
220220
expect(workflow).toContain(
221221
'CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }}'
222222
);
223-
expect(workflow).toContain('codex-oauth-ok');
223+
expect(workflow).not.toContain('codex-oauth-ok');
224224
expect(workflow).toContain('CODEX_MODEL: ${{ vars.REVIEW_CODEX_MODEL }}');
225225
expect(workflow).not.toContain(
226226
'REVIEW_PROVIDERS: ${{ vars.REVIEW_PROVIDERS }}'
227227
);
228228
expect(workflow).toContain("INLINE_MAX_COMMENTS: '5'");
229229
expect(workflow).toContain("INLINE_MIN_SEVERITY: 'major'");
230230
expect(workflow).toContain("CODEX_REASONING_EFFORT: 'medium'");
231+
expect(workflow).toContain("CODEX_HEALTHCHECK_MODE: 'binary'");
231232
expect(workflow).toContain("CODEX_AGENTIC_CONTEXT: 'true'");
232233
expect(workflow).toContain('UPDATE_PR_DESCRIPTION:');
233234
expect(workflow).toContain("FAIL_ON_CRITICAL: 'true'");
@@ -473,7 +474,8 @@ describe('review-router curl installer e2e', () => {
473474
expect(result.status).toBe(0);
474475
const workflow = workflowText(result.workflowPath);
475476
expect(workflow).toContain('OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}');
476-
expect(workflow).toContain('codex-api-ok');
477+
expect(workflow).toContain('Validate OpenAI API key secret');
478+
expect(workflow).not.toContain('codex-api-ok');
477479
expect(workflow).toContain('CODEX_MODEL: ${{ vars.REVIEW_CODEX_MODEL }}');
478480
expect(workflow).not.toContain(
479481
'REVIEW_PROVIDERS: ${{ vars.REVIEW_PROVIDERS }}'
@@ -484,6 +486,7 @@ describe('review-router curl installer e2e', () => {
484486
expect(workflow).toContain("FAIL_ON_MAJOR: 'true'");
485487
expect(workflow).not.toContain('FAIL_ON_SEVERITY:');
486488
expect(workflow).toContain("CODEX_REASONING_EFFORT: 'high'");
489+
expect(workflow).toContain("CODEX_HEALTHCHECK_MODE: 'binary'");
487490
expect(workflow).toContain("CODEX_AGENTIC_CONTEXT: 'true'");
488491
expect(workflow).toContain("GRAPH_ENABLED: 'true'");
489492
expect(workflow).not.toContain('CODEX_AUTH_JSON');

__tests__/unit/providers/codex-provider.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,47 @@ describe('CodexProvider', () => {
6161
expect(args).not.toContain('--dangerously-bypass-approvals-and-sandbox');
6262
});
6363

64+
it('uses binary-only health checks by default to avoid consuming Codex usage', async () => {
65+
spawnMock.mockImplementation((_cmd: string, _args: string[]) =>
66+
createMockProcess()
67+
);
68+
69+
const provider = new CodexProvider('gpt-5.4-mini');
70+
await expect(provider.healthCheck(1000)).resolves.toBe(true);
71+
72+
expect(spawnMock).toHaveBeenCalledTimes(1);
73+
expect(spawnMock.mock.calls[0][0]).toBe('codex');
74+
expect(spawnMock.mock.calls[0][1]).toEqual(['--version']);
75+
expect(
76+
spawnMock.mock.calls.some(
77+
(call) => Array.isArray(call[1]) && call[1][0] === 'exec'
78+
)
79+
).toBe(false);
80+
});
81+
82+
it('supports explicit exec health checks when requested', async () => {
83+
process.env.CODEX_HEALTHCHECK_MODE = 'exec';
84+
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
85+
if (args.includes('--version')) {
86+
return createMockProcess();
87+
}
88+
89+
return createMockProcess(() => {
90+
const outputIndex = args.indexOf('--output-last-message');
91+
fs.writeFileSync(args[outputIndex + 1], 'codex-health-ok');
92+
});
93+
});
94+
95+
const provider = new CodexProvider('gpt-5.4-mini');
96+
await expect(provider.healthCheck(1000)).resolves.toBe(true);
97+
98+
const execCall = spawnMock.mock.calls.find(
99+
(call) => Array.isArray(call[1]) && call[1][0] === 'exec'
100+
);
101+
expect(execCall).toBeTruthy();
102+
expect(execCall?.[1]).toContain('gpt-5.4-mini');
103+
});
104+
64105
it('can disable interactive/tool features for isolated discussion prompts', () => {
65106
const provider = new CodexProvider('gpt-5.4-mini');
66107
const args = (provider as any).buildExecArgs({

action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ inputs:
113113
CODEX_HEALTHCHECK_MODE:
114114
description: "Codex provider health check mode: exec, binary, or none"
115115
required: false
116-
default: "exec"
116+
default: "binary"
117117
CODEX_HEALTHCHECK_REASONING_EFFORT:
118118
description: "Codex reasoning effort used for health checks"
119119
required: false

dist/index.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13152,12 +13152,12 @@ var CodexProvider = class extends Provider {
1315213152
this.model = model;
1315313153
this.options = options;
1315413154
}
13155-
// Verify the CLI is available and, by default, that the selected model works
13156-
// with the current Codex auth. Binary-only checks can mark unsupported models
13157-
// as healthy, which then creates green "no provider" review runs.
13155+
// Verify the CLI is available. Model/auth failures are surfaced by the real
13156+
// review call; a model-exec health check costs an extra Codex subscription
13157+
// request and can exhaust limited OAuth usage before review starts.
1315813158
async healthCheck(_timeoutMs = 5e3) {
1315913159
const timeoutMs = Math.max(500, _timeoutMs ?? 5e3);
13160-
const mode = (process.env.CODEX_HEALTHCHECK_MODE || "exec").toLowerCase();
13160+
const mode = (process.env.CODEX_HEALTHCHECK_MODE || "binary").toLowerCase();
1316113161
let timeoutId;
1316213162
let isTimedOut = false;
1316313163
const timeoutPromise = new Promise((_, reject) => {
@@ -27206,7 +27206,7 @@ function failureDetails(kind) {
2720627206
summary: "The Codex CLI could not run successfully in CI.",
2720727207
steps: [
2720827208
"Verify the workflow installs `@openai/codex` before running ReviewRouter.",
27209-
"Check the `Verify Codex OAuth headless mode` or `Verify Codex API key headless mode` step.",
27209+
"Check the ReviewRouter run logs for the Codex CLI error. Usage-limit errors usually need a later rerun or a lower-cost model.",
2721027210
"If this is a model issue, verify `REVIEW_CODEX_MODEL` is a current supported Codex model."
2721127211
]
2721227212
};
@@ -27215,7 +27215,7 @@ function failureDetails(kind) {
2721527215
summary: "No configured review provider passed the health check.",
2721627216
steps: [
2721727217
"Check provider credentials and model variables.",
27218-
"For Codex OAuth, verify the smoke step can run `codex exec` headlessly.",
27218+
"For Codex OAuth, verify `CODEX_AUTH_JSON` is present and the account has available Codex usage.",
2721927219
"For OpenRouter or OpenAI API mode, verify the API key secret is available to this repository."
2722027220
]
2722127221
};

dist/index.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/install.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,21 @@ By default, the installer does not copy `~/.codex/config.toml`. Local Codex conf
222222
REVIEW_ROUTER_INCLUDE_CODEX_CONFIG=1 bash scripts/install.sh
223223
```
224224

225-
The generated workflow installs the official Codex CLI and runs a headless smoke check before review:
225+
The generated workflow installs the official Codex CLI and restores OAuth credentials before review. It does not run a separate headless smoke prompt by default because that would consume an extra Codex subscription request on every PR run and rerun.
226226

227227
```yaml
228-
- name: Verify Codex OAuth headless mode
228+
- name: Restore Codex OAuth config
229+
env:
230+
CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }}
229231
run: |
230-
codex exec --model "$CODEX_MODEL" --sandbox read-only --ephemeral --ignore-user-config -c approval_policy=never -c model_reasoning_effort='"low"' --output-last-message /tmp/codex-smoke.txt "Respond with exactly: codex-oauth-ok"
231-
grep -q "codex-oauth-ok" /tmp/codex-smoke.txt
232+
test -n "$CODEX_AUTH_JSON"
233+
mkdir -p ~/.codex
234+
printf '%s' "$CODEX_AUTH_JSON" > ~/.codex/auth.json
235+
chmod 600 ~/.codex/auth.json
232236
```
233237
238+
ReviewRouter uses a binary-only Codex health check by default. The real review call is the authoritative auth/model check. If you explicitly want an extra model-exec health check, set `CODEX_HEALTHCHECK_MODE=exec`, but expect higher subscription usage.
239+
234240
Use this only in trusted automation. Do not put personal Codex OAuth credentials into public/open-source repos where untrusted workflow changes can access secrets. GitHub does not expose repository secrets to fork PR workflows by default, and the generated workflow skips fork PRs by default.
235241

236242
Default Codex model:

scripts/install.sh

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,25 +1117,15 @@ YAML
11171117
printf '%s' "$CODEX_CONFIG_TOML" > ~/.codex/config.toml
11181118
chmod 600 ~/.codex/config.toml
11191119
fi
1120-
1121-
- name: Verify Codex OAuth headless mode
1122-
env:
1123-
CODEX_MODEL: ${{ vars.REVIEW_CODEX_MODEL }}
1124-
run: |
1125-
codex exec --model "$CODEX_MODEL" --sandbox read-only --ephemeral --ignore-user-config -c approval_policy=never -c model_reasoning_effort='"low"' --output-last-message /tmp/codex-smoke.txt "Respond with exactly: codex-oauth-ok"
1126-
grep -q "codex-oauth-ok" /tmp/codex-smoke.txt
11271120
YAML
11281121
elif [ "$AUTH_MODE" = "openai" ]; then
11291122
cat <<'YAML'
11301123
1131-
- name: Verify Codex API key headless mode
1124+
- name: Validate OpenAI API key secret
11321125
env:
1133-
CODEX_MODEL: ${{ vars.REVIEW_CODEX_MODEL }}
11341126
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
11351127
run: |
11361128
test -n "$OPENAI_API_KEY"
1137-
codex exec --model "$CODEX_MODEL" --sandbox read-only --ephemeral --ignore-user-config -c approval_policy=never -c model_reasoning_effort='"low"' --output-last-message /tmp/codex-smoke.txt "Respond with exactly: codex-api-ok"
1138-
grep -q "codex-api-ok" /tmp/codex-smoke.txt
11391129
YAML
11401130
fi
11411131

@@ -1198,6 +1188,7 @@ YAML
11981188
YAML
11991189
cat <<YAML
12001190
CODEX_REASONING_EFFORT: '$CODEX_REASONING_EFFORT'
1191+
CODEX_HEALTHCHECK_MODE: 'binary'
12011192
CODEX_AGENTIC_CONTEXT: 'true'
12021193
YAML
12031194
elif [ "$AUTH_MODE" = "openrouter" ]; then

src/github/failure-summary.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ function failureDetails(kind: FailureKind): { summary: string; steps: string[] }
115115
summary: 'The Codex CLI could not run successfully in CI.',
116116
steps: [
117117
'Verify the workflow installs `@openai/codex` before running ReviewRouter.',
118-
'Check the `Verify Codex OAuth headless mode` or `Verify Codex API key headless mode` step.',
118+
'Check the ReviewRouter run logs for the Codex CLI error. Usage-limit errors usually need a later rerun or a lower-cost model.',
119119
'If this is a model issue, verify `REVIEW_CODEX_MODEL` is a current supported Codex model.',
120120
],
121121
};
@@ -124,7 +124,7 @@ function failureDetails(kind: FailureKind): { summary: string; steps: string[] }
124124
summary: 'No configured review provider passed the health check.',
125125
steps: [
126126
'Check provider credentials and model variables.',
127-
'For Codex OAuth, verify the smoke step can run `codex exec` headlessly.',
127+
'For Codex OAuth, verify `CODEX_AUTH_JSON` is present and the account has available Codex usage.',
128128
'For OpenRouter or OpenAI API mode, verify the API key secret is available to this repository.',
129129
],
130130
};

src/providers/codex.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ export class CodexProvider extends Provider {
3838
super(`codex/${model}`);
3939
}
4040

41-
// Verify the CLI is available and, by default, that the selected model works
42-
// with the current Codex auth. Binary-only checks can mark unsupported models
43-
// as healthy, which then creates green "no provider" review runs.
41+
// Verify the CLI is available. Model/auth failures are surfaced by the real
42+
// review call; a model-exec health check costs an extra Codex subscription
43+
// request and can exhaust limited OAuth usage before review starts.
4444
async healthCheck(_timeoutMs: number = 5000): Promise<boolean> {
4545
const timeoutMs = Math.max(500, _timeoutMs ?? 5000);
46-
const mode = (process.env.CODEX_HEALTHCHECK_MODE || 'exec').toLowerCase();
46+
const mode = (process.env.CODEX_HEALTHCHECK_MODE || 'binary').toLowerCase();
4747

4848
let timeoutId: NodeJS.Timeout;
4949
let isTimedOut = false;

0 commit comments

Comments
 (0)