Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Available but still experimental:
- **GitHub identity options:** post as `github-actions[bot]` or as your own GitHub App bot.
- **Reusable App profiles:** create a GitHub App once, then reuse its saved local profile for more repositories.
- **Read-only agentic context:** Codex starts from the PR diff, then may inspect related repository files in a read-only sandbox.
- **Bounded agentic retries:** normal reviews can retry once when repository exploration is missing, but oversized prompts are not run twice unless strict audit mode is enabled.
- **Strict JSON findings:** provider output is parsed into `{file,line,severity,title,message,suggestion}` before posting.
- **Inline comments:** posts only valid comments on changed lines, with severity labels in the comment body.
- **Progress comment:** shows live progress until the PR has a ReviewRouter result; clean first runs become an all-clear summary.
Expand Down
40 changes: 39 additions & 1 deletion __tests__/unit/providers/codex-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,44 @@ describe('CodexProvider', () => {
expect(result.findings).toEqual([]);
});

it('strict agentic audit reruns once, then fails if exploration is still missing', async () => {
it('does not repeat an oversized prompt only to satisfy the optional agentic audit', async () => {
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
if (args.includes('--version')) {
return createMockProcess();
}

return createMockProcess(() => {
const outputIndex = args.indexOf('--output-last-message');
fs.writeFileSync(
args[outputIndex + 1],
'{"findings":[],"revalidations":[]}'
);
});
});

const provider = new CodexProvider('gpt-5.4-mini', {
agenticContext: true,
});
const result = await provider.review(
[
'Files changed:',
'- src/generated.ts (modified, +1/-1)',
'',
'Diff:',
'diff --git a/src/generated.ts b/src/generated.ts',
'x'.repeat(120_000),
].join('\n'),
1000
);
const execCalls = spawnMock.mock.calls.filter(
(call) => Array.isArray(call[1]) && call[1][0] === 'exec'
);

expect(execCalls).toHaveLength(1);
expect(result.findings).toEqual([]);
});

it('strict agentic audit reruns an oversized prompt, then fails if exploration is still missing', async () => {
process.env.CODEX_AGENTIC_AUDIT = 'strict';
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
if (args.includes('--version')) {
Expand All @@ -905,6 +942,7 @@ describe('CodexProvider', () => {
'',
'Diff:',
'diff --git a/src/app.ts b/src/app.ts',
'x'.repeat(120_000),
].join('\n'),
1000
)
Expand Down
14 changes: 13 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13575,6 +13575,7 @@ function safeOutput(value) {
}

// src/providers/codex.ts
var MAX_OPTIONAL_AGENTIC_RETRY_PROMPT_TOKENS = 24e3;
var CodexCliExitError = class extends Error {
constructor(code, stdout, stderr, message) {
super(message);
Expand Down Expand Up @@ -14016,7 +14017,18 @@ var CodexProvider = class _CodexProvider extends Provider {
return parseReviewOutputStrict(content, "Codex CLI");
}
shouldRetryForMissingAgenticExploration(parsed, audit, prompt, mode) {
return (mode === "rerun" || mode === "strict") && this.isMissingAgenticExploration(parsed, audit, prompt);
if (mode !== "rerun" && mode !== "strict" || !this.isMissingAgenticExploration(parsed, audit, prompt)) {
return false;
}
if (mode === "strict") return true;
const promptTokens = estimateTokensSimple(prompt).tokens;
if (promptTokens > MAX_OPTIONAL_AGENTIC_RETRY_PROMPT_TOKENS) {
logger.info(
`Skipping optional Codex agentic retry for ${this.name}: prompt is approximately ${promptTokens} tokens (limit ${MAX_OPTIONAL_AGENTIC_RETRY_PROMPT_TOKENS})`
);
return false;
}
return true;
}
isMissingAgenticExploration(parsed, audit, prompt) {
if (!this.looksLikePullRequestReviewPrompt(prompt)) return false;
Expand Down
4 changes: 2 additions & 2 deletions dist/index.js.map

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions src/providers/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type CodexRunResult = {

type CodexAgenticAuditMode = 'off' | 'rerun' | 'strict';

const MAX_OPTIONAL_AGENTIC_RETRY_PROMPT_TOKENS = 24_000;

type CodexAgenticAudit = {
commandExecutions: number;
readOnlyExplorationCommands: number;
Expand Down Expand Up @@ -615,10 +617,22 @@ export class CodexProvider extends Provider {
prompt: string,
mode: CodexAgenticAuditMode
): boolean {
return (
(mode === 'rerun' || mode === 'strict') &&
this.isMissingAgenticExploration(parsed, audit, prompt)
);
if (
(mode !== 'rerun' && mode !== 'strict') ||
!this.isMissingAgenticExploration(parsed, audit, prompt)
) {
return false;
}
if (mode === 'strict') return true;

const promptTokens = estimateTokensSimple(prompt).tokens;
if (promptTokens > MAX_OPTIONAL_AGENTIC_RETRY_PROMPT_TOKENS) {
logger.info(
`Skipping optional Codex agentic retry for ${this.name}: prompt is approximately ${promptTokens} tokens (limit ${MAX_OPTIONAL_AGENTIC_RETRY_PROMPT_TOKENS})`
);
return false;
}
return true;
}

private isMissingAgenticExploration(
Expand Down
Loading