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
3 changes: 2 additions & 1 deletion .github/scripts/pr-review/prepare.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const maxDiffBytes = positiveInteger("MAX_DIFF_BYTES");
const diffReadBufferBytes = maxDiffBytes + 64 * 1024;
const automaticMaxDiffBytes = positiveInteger("AUTOMATIC_MAX_DIFF_BYTES");
const largeDiffApproved = required("LARGE_DIFF_APPROVED") === "true";
const privateRepository = required("PRIVATE_REPOSITORY") === "true";
const chunkTargetBytes = positiveInteger("CHUNK_TARGET_BYTES");
const ledgerPath = path.join(stateDir, "review-ledger.json");
const generationsDir = path.join(stateDir, "generations");
Expand Down Expand Up @@ -97,7 +98,7 @@ if (fullDiff.length > maxDiffBytes) {
`Pull-request diff is ${fullDiff.length} bytes; the configured total limit is ${maxDiffBytes} bytes.`,
);
}
if (!largeDiffApproved && fullDiff.length > automaticMaxDiffBytes) {
if (!privateRepository && !largeDiffApproved && fullDiff.length > automaticMaxDiffBytes) {
throw new Error(
`Pull-request diff is ${fullDiff.length} bytes; automatic reviews are limited to ${automaticMaxDiffBytes} bytes. A repository admin must comment @codex review approve ${headSha} on this PR to review this head.`,
);
Expand Down
29 changes: 22 additions & 7 deletions .github/scripts/pr-review/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ assert.match(workflowSource, /^ review:\n(?:(?!^ \S)[\s\S])*?^ REVIEW_MAX
assert.match(workflowSource, /^ review:\n(?:(?!^ \S)[\s\S])*?^ REVIEW_AUTOMATIC_MAX_DIFF_BYTES: '5000000'$/m);
assert.match(workflowSource, /AUTOMATIC_MAX_DIFF_BYTES: \$\{\{ env\.REVIEW_AUTOMATIC_MAX_DIFF_BYTES \}\}/);
assert.match(workflowSource, /LARGE_DIFF_APPROVED: \$\{\{ needs\.resolve\.outputs\.large_diff_approved \}\}/);
assert.match(workflowSource, /PRIVATE_REPOSITORY: \$\{\{ needs\.resolve\.outputs\.private_repository \}\}/);
assert.match(workflowSource, /private_repository', String\(pr\.base\.repo\?\.private === true\)/);
assert.match(workflowSource, /getCollaboratorPermissionLevel/);
assert.match(workflowSource, /^ publish:\n(?:(?!^ \S)[\s\S])*?^ REVIEW_MODEL: gpt-6-sol$/m);
assert.match(workflowSource, /^ publish:\n(?:(?!^ \S)[\s\S])*?^ REVIEW_EFFORT: low$/m);
Expand Down Expand Up @@ -526,7 +528,7 @@ try {
});
assert.equal(plainPatch.status, 0);
assert.ok(plainPatch.stdout.length < 1000);
const prepareBinary = (approved) => spawnSync(process.execPath, [
const prepareBinary = (approved, privateRepo = false, hardMax = "20000") => spawnSync(process.execPath, [
path.join(path.dirname(new URL(import.meta.url).pathname), "prepare.mjs"),
], {
cwd: binaryRepo,
Expand All @@ -538,9 +540,10 @@ try {
PR_BASE_SHA: binaryBase,
PR_HEAD_SHA: binaryHead,
SESSION_KEY: "repo:1:pr:binary:v2",
MAX_DIFF_BYTES: "20000",
MAX_DIFF_BYTES: hardMax,
AUTOMATIC_MAX_DIFF_BYTES: "4000",
LARGE_DIFF_APPROVED: String(approved),
PRIVATE_REPOSITORY: String(privateRepo),
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "binary-context",
},
Expand All @@ -549,6 +552,10 @@ try {
assert.notEqual(blockedBinary.status, 0);
assert.match(blockedBinary.stderr, /automatic reviews are limited to 4000 bytes/);
assert.equal(prepareBinary(true).status, 0);
assert.equal(prepareBinary(false, true).status, 0);
const blockedPrivateHardCap = prepareBinary(false, true, "4000");
assert.notEqual(blockedPrivateHardCap.status, 0);
assert.match(blockedPrivateHardCap.stderr, /configured total limit is 4000 bytes/);

const repo = path.join(temporary, "repo");
fs.mkdirSync(repo);
Expand Down Expand Up @@ -590,12 +597,13 @@ try {
MAX_DIFF_BYTES: "1000000",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
PRIVATE_REPOSITORY: "false",
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1",
},
});
assert.equal(result.status, 0, result.stderr);
const prepareWithPolicy = (automaticMax, approved, hardMax = "1000000") =>
const prepareWithPolicy = (automaticMax, approved, hardMax = "1000000", privateRepo = false) =>
spawnSync(process.execPath, [
path.join(path.dirname(new URL(import.meta.url).pathname), "prepare.mjs"),
], {
Expand All @@ -611,6 +619,7 @@ try {
MAX_DIFF_BYTES: hardMax,
AUTOMATIC_MAX_DIFF_BYTES: automaticMax,
LARGE_DIFF_APPROVED: String(approved),
PRIVATE_REPOSITORY: String(privateRepo),
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1",
},
Expand All @@ -619,6 +628,7 @@ try {
assert.notEqual(blockedAutomatic.status, 0);
assert.match(blockedAutomatic.stderr, /automatic reviews are limited to 1000 bytes/);
assert.equal(prepareWithPolicy("1000", true).status, 0);
assert.equal(prepareWithPolicy("1000", false, "1000000", true).status, 0);
const blockedHardCap = prepareWithPolicy("1000", true, "1000");
assert.notEqual(blockedHardCap.status, 0);
assert.match(blockedHardCap.stderr, /configured total limit is 1000 bytes/);
Expand Down Expand Up @@ -660,6 +670,7 @@ try {
MAX_DIFF_BYTES: "1000000",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
PRIVATE_REPOSITORY: "false",
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1",
},
Expand Down Expand Up @@ -697,6 +708,7 @@ try {
MAX_DIFF_BYTES: "1000000",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
PRIVATE_REPOSITORY: "false",
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1",
},
Expand Down Expand Up @@ -757,8 +769,9 @@ try {
PR_HEAD_SHA: mergedHead,
SESSION_KEY: "repo:1:pr:2:v2",
MAX_DIFF_BYTES: "1000000",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
PRIVATE_REPOSITORY: "false",
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1",
};
Expand Down Expand Up @@ -1106,8 +1119,9 @@ fs.writeFileSync(outputFile, JSON.stringify(result));
env: { ...process.env, REPOSITORY_DIR: repo, PR_REVIEW_STATE_DIR: legacyState,
PR_BASE_SHA: base, PR_HEAD_SHA: nextHead, SESSION_KEY: "repo:1:pr:2:v2",
MAX_DIFF_BYTES: "1000000",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false", CHUNK_TARGET_BYTES: "600",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
PRIVATE_REPOSITORY: "false", CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1", GITHUB_OUTPUT: output },
});
assert.equal(prepared.status, 0, prepared.stderr);
Expand Down Expand Up @@ -1459,6 +1473,7 @@ fs.writeFileSync(outputFile, JSON.stringify(result));
MAX_DIFF_BYTES: "1000000",
AUTOMATIC_MAX_DIFF_BYTES: "1000000",
LARGE_DIFF_APPROVED: "false",
PRIVATE_REPOSITORY: "false",
CHUNK_TARGET_BYTES: "600",
READINESS_CONTEXT_SHA256: "context-v1",
},
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/codex-openai-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ jobs:
head_sha: ${{ steps.pr.outputs.head_sha }}
request_comment_id: ${{ steps.pr.outputs.request_comment_id }}
large_diff_approved: ${{ steps.pr.outputs.large_diff_approved }}
private_repository: ${{ steps.pr.outputs.private_repository }}
steps:
- name: Check out the exact comment-trigger implementation
if: github.event_name == 'issue_comment'
Expand Down Expand Up @@ -193,6 +194,7 @@ jobs:
}
core.setOutput('eligible', String(eligible));
core.setOutput('large_diff_approved', String(largeDiffApproved));
core.setOutput('private_repository', String(pr.base.repo?.private === true));
core.setOutput('number', String(pr.number));
core.setOutput('base_sha', pr.base.sha);
core.setOutput('base_ref', pr.base.ref);
Expand Down Expand Up @@ -881,6 +883,7 @@ jobs:
MAX_DIFF_BYTES: ${{ env.REVIEW_MAX_DIFF_BYTES }}
AUTOMATIC_MAX_DIFF_BYTES: ${{ env.REVIEW_AUTOMATIC_MAX_DIFF_BYTES }}
LARGE_DIFF_APPROVED: ${{ needs.resolve.outputs.large_diff_approved }}
PRIVATE_REPOSITORY: ${{ needs.resolve.outputs.private_repository }}
CHUNK_TARGET_BYTES: ${{ inputs.chunk-target-bytes }}
READINESS_CONTEXT_SHA256: ${{ steps.prepare_readiness.outputs.context_sha256 }}
run: node "$REVIEWER_SOURCE_DIR/.github/scripts/pr-review/prepare.mjs"
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ and `actions: write` so the reviewer can restore the latest per-PR Codex
session artifact and delete superseded snapshots only after a replacement
upload succeeds.

Complete binary-aware Git patches up to 5,000,000 bytes are reviewed
automatically. Binary models, images, and other changed binary payloads count
toward this limit even though their contents are not sent to the model. For a larger
diff, an admin of the **calling repository** must post this line as a PR comment
with the current full head commit SHA:
In public caller repositories, complete binary-aware Git patches up to
5,000,000 bytes are reviewed automatically. Binary models, images, and other
changed binary payloads count toward this limit even though their contents are
not sent to the model. For a larger public-repository diff, an admin of the
**calling repository** must post this line as a PR comment with the current
full head commit SHA:

```text
@codex review approve <40-character-head-sha>
Expand All @@ -45,8 +46,11 @@ with the current full head commit SHA:
The shared reviewer checks the comment author's admin permission in the caller
repository and the live head SHA before model work. A push requires a new
approval. This uses the existing `issue_comment` trigger and works for public
and private callers without a GitHub Environment or per-repository approver
configuration. Diffs above 100,000,000 bytes remain rejected.
callers without a GitHub Environment or per-repository approver configuration.
Private caller repositories skip the 5,000,000-byte approval gate and review
automatically. The caller's base repository privacy comes from GitHub's PR API;
unknown privacy is treated as public. Diffs above 100,000,000 bytes remain
rejected in every repository.

## Behavior

Expand Down Expand Up @@ -155,7 +159,7 @@ configuration. Diffs above 100,000,000 bytes remain rejected.
`pull_request_target` workflow and use the caller's explicitly forwarded
secret. Secrets from the contributor's fork are not imported or used.
- Automatic PR events permit an external contributor to consume review
requests up to the 5,000,000-byte automatic limit. Use a
requests up to the 5,000,000-byte automatic limit in public callers. Use a
dedicated API project with appropriate usage limits and restrict the
organization secret to selected repositories.
- Complete diffs larger than 100,000,000 bytes fail before Codex runs. The
Expand Down
Loading