Skip to content

Instantly share code, notes, and snippets.

@mahmoudimus
Last active July 3, 2026 17:17
Show Gist options
  • Select an option

  • Save mahmoudimus/79e9aec183602e18045d64c5678465a4 to your computer and use it in GitHub Desktop.

Select an option

Save mahmoudimus/79e9aec183602e18045d64c5678465a4 to your computer and use it in GitHub Desktop.
Post alibaba's [open-code-review](https://github.com/alibaba/open-code-review) to a github PR
#!/usr/bin/env node
const fs = require("fs");
const crypto = require("crypto");
const { spawnSync } = require("child_process");
const opts = parseArgs(process.argv.slice(2));
if (opts.help) {
usage(0);
}
const resultPath = opts.result || process.env.OCR_RESULT_PATH || "/tmp/ocr-result.json";
const stderrPath = opts.stderr || process.env.OCR_STDERR_PATH || "/tmp/ocr-stderr.log";
const repo = opts.repo || process.env.GITHUB_REPOSITORY || inferRepo();
const prNumber = Number(opts.pr || process.env.PR_NUMBER || process.env.GITHUB_REF_NAME);
if (!repo || !/^[^/]+\/[^/]+$/.test(repo)) {
fail("Missing --repo owner/name and unable to infer it from git remote.");
}
if (!Number.isFinite(prNumber) || prNumber <= 0) {
fail("Missing --pr <number>.");
}
const [owner, repoName] = repo.split("/");
const runId = cleanRunPart(opts.runId || process.env.GITHUB_RUN_ID || "local");
const runAttempt = cleanRunPart(opts.runAttempt || process.env.GITHUB_RUN_ATTEMPT || "1");
const RUN_TAG = `${runId}-${runAttempt}`;
const REVIEW_TAG = `<!-- ocr-review-run:${RUN_TAG} -->`;
const SUMMARY_TAG = `<!-- ocr-summary-run:${RUN_TAG} -->`;
const MAX_RETRIES = parseNonNegInt(process.env.OCR_MAX_RETRIES, 3);
const SUCCESS_DELAY = parseNonNegInt(process.env.OCR_SUCCESS_DELAY, 2000);
const FAILURE_DELAY = parseNonNegInt(process.env.OCR_FAILURE_DELAY, 1000);
const LOW_REMAINING_THRESHOLD = parseNonNegInt(process.env.OCR_LOW_REMAINING_THRESHOLD, 3);
const LOW_REMAINING_SPACING = parseNonNegInt(process.env.OCR_LOW_REMAINING_SPACING, 10000);
const READ_SUCCESS_DELAY = parseNonNegInt(process.env.OCR_READ_SUCCESS_DELAY, 500);
const READ_LOW_REMAINING_SPACING = parseNonNegInt(process.env.OCR_READ_LOW_REMAINING_SPACING, 5000);
main().catch((e) => {
console.error(e && e.stack ? e.stack : String(e));
process.exit(1);
});
async function main() {
let result;
try {
result = JSON.parse(fs.readFileSync(resultPath, "utf8"));
} catch (e) {
console.log("Failed to parse OCR output:", e.message);
const stderr = readIfExists(stderrPath).trim();
if (stderr) {
await createIssueComment(`OpenCodeReview encountered an error:\n${fencedBlock(stderr)}`);
}
return;
}
const comments = result.comments || [];
const warnings = result.warnings || [];
if (comments.length === 0) {
const message = result.message || "No comments generated. Looks good to me.";
await createIssueComment(`OpenCodeReview: ${message}`);
return;
}
const pullRequest = getPullRequest();
const commitSha = opts.commit || process.env.HEAD_SHA || pullRequest.head.sha;
const reviewComments = [];
const commentsWithoutLine = [];
for (const comment of comments) {
const hasValidLine = (comment.start_line >= 1) || (comment.end_line >= 1);
if (!hasValidLine) {
commentsWithoutLine.push({ comment });
continue;
}
reviewComments.push({
comment,
id: newCommentId(),
lines: resolveLines(comment),
});
}
const totalCount = comments.length;
let summaryBody = buildSummaryBody(totalCount, reviewComments.length, commentsWithoutLine.length, warnings);
summaryBody += formatSummaryComments(commentsWithoutLine);
summaryBody = REVIEW_TAG + "\n" + summaryBody;
let successCount = 0;
let failedCount = 0;
const failedComments = [];
try {
const batchRes = await createReview({
commit_id: commitSha,
body: summaryBody,
event: "COMMENT",
comments: reviewComments.map(toReviewPayload),
});
successCount = reviewComments.length;
console.log(`Successfully posted review with ${successCount} inline comments (${commentsWithoutLine.length} in summary)`);
logRateLimitQuota(batchRes, "after batch createReview");
} catch (e) {
console.log("Failed to post review with inline comments:", e.message);
console.log("Checking whether the batch review actually landed on the server before retrying...");
let existingReview = null;
try {
existingReview = await findExistingBatchReview({ tag: REVIEW_TAG });
} catch (checkErr) {
console.log(`Idempotency check failed (${checkErr.message}). Degrading to fallback.`);
}
let toRetry = reviewComments;
if (existingReview && existingReview.found) {
const postedIds = await getPostedCommentIds();
toRetry = reviewComments.filter((item) => !postedIds.has(item.id));
successCount = reviewComments.length - toRetry.length;
console.log(`Batch review already exists (review_id=${existingReview.review.id}). ${successCount}/${reviewComments.length} inline comments already posted. ${toRetry.length} missing.`);
} else {
console.log("Batch review not found on server. Falling back to per-comment posting...");
}
const batchRetry = computeRetryDelayMs(e, 0);
if (batchRetry != null) {
console.log(`Batch createReview was rate-limited/transient. Cooling down ${(batchRetry.delayMs / 1000).toFixed(1)}s via ${batchRetry.source} (${batchRetry.detail}).`);
await sleep(batchRetry.delayMs);
}
for (const item of toRetry) {
const { comment, id } = item;
let posted = false;
for (let attempt = 0; attempt <= MAX_RETRIES && !posted; attempt++) {
try {
const res = await createReview({
commit_id: commitSha,
body: "",
event: "COMMENT",
comments: [toReviewPayload(item)],
});
successCount++;
posted = true;
console.log(`Successfully posted comment for ${comment.path}`);
const remaining = logRateLimitQuota(res, `after ${comment.path}`);
if (remaining != null && remaining <= LOW_REMAINING_THRESHOLD) {
await sleep(LOW_REMAINING_SPACING);
} else {
await sleep(SUCCESS_DELAY);
}
} catch (innerE) {
const retryInfo = computeRetryDelayMs(innerE, attempt);
const willRetry = retryInfo != null && attempt < MAX_RETRIES;
const status = innerE.status;
const maybeReachedServer =
(typeof status === "number" && (status >= 500 || status === 408)) || status == null;
if (maybeReachedServer) {
const coolDownMs = retryInfo != null ? retryInfo.delayMs : FAILURE_DELAY;
if (coolDownMs > 0) {
console.log(`Cooling down ${(coolDownMs / 1000).toFixed(1)}s before idempotency check for ${comment.path}.`);
await sleep(coolDownMs);
}
const alreadyPosted = await isCommentAlreadyPosted({ id });
if (alreadyPosted === true) {
successCount++;
posted = true;
console.log(`Comment for ${comment.path} already posted (id=${id}); treating as success.`);
await sleep(SUCCESS_DELAY);
continue;
}
if (alreadyPosted === null) {
failedCount++;
failedComments.push({ comment, error: `${innerE.message} [idempotency check unavailable]` });
console.log(`Cannot verify whether comment for ${comment.path} was posted; skipping retry to avoid duplicate.`);
await sleep(SUCCESS_DELAY);
break;
}
if (!willRetry) {
failedCount++;
failedComments.push({ comment, error: innerE.message });
console.log(`Failed to post comment for ${comment.path}: ${innerE.message}`);
await sleep(SUCCESS_DELAY);
break;
}
} else if (willRetry) {
console.log(`Rate-limited/transient on ${comment.path}. Waiting ${(retryInfo.delayMs / 1000).toFixed(1)}s via ${retryInfo.source}.`);
await sleep(retryInfo.delayMs);
} else {
failedCount++;
failedComments.push({ comment, error: innerE.message });
console.log(`Failed to post comment for ${comment.path}: ${innerE.message}`);
await sleep(FAILURE_DELAY);
break;
}
}
}
}
let finalBody = buildSummaryBody(totalCount, successCount, commentsWithoutLine.length + failedComments.length, warnings);
finalBody += formatSummaryComments(commentsWithoutLine);
finalBody += "\n\n---\n\nPosting statistics:";
finalBody += `\n- Successfully posted: ${successCount} comment(s)`;
if (failedCount > 0) {
finalBody += `\n- Failed to post: ${failedCount} comment(s)`;
}
if (failedComments.length > 0) {
finalBody += "\n\n---\n\n### Inline comments shown in summary";
for (const { comment, error } of failedComments) {
finalBody += "\n\n---\n\n";
finalBody += formatCommentMarkdown(comment, error);
}
}
finalBody = SUMMARY_TAG + "\n" + finalBody;
const summaryAlreadyPosted = await hasIssueCommentWithId({ id: SUMMARY_TAG });
if (summaryAlreadyPosted === true) {
console.log("Summary comment with this run tag already exists; skipping.");
} else if (summaryAlreadyPosted === null) {
console.log("Cannot verify whether summary comment already exists; skipping to avoid duplicate.");
} else {
await createIssueComment(finalBody);
}
}
}
function parseArgs(args) {
const out = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "-h" || arg === "--help") out.help = true;
else if (arg === "--repo") out.repo = args[++i];
else if (arg === "--pr") out.pr = args[++i];
else if (arg === "--result") out.result = args[++i];
else if (arg === "--stderr") out.stderr = args[++i];
else if (arg === "--commit") out.commit = args[++i];
else if (arg === "--run-id") out.runId = args[++i];
else if (arg === "--run-attempt") out.runAttempt = args[++i];
else fail(`Unknown argument: ${arg}`);
}
return out;
}
function usage(code) {
const text = [
"Usage:",
" opencodereview-post-to-pr --repo owner/repo --pr 123 [--result /tmp/ocr-result.json] [--stderr /tmp/ocr-stderr.log]",
"",
"Typical local flow:",
" git fetch origin 'refs/pull/123/head:pr-123'",
" opencodereview review --from origin/main --to pr-123 --format json > /tmp/ocr-result.json 2>/tmp/ocr-stderr.log || true",
" GITHUB_TOKEN= opencodereview-post-to-pr --repo _ORG_/_REPO_ --pr 123",
].join("\n");
console.log(text);
process.exit(code);
}
function inferRepo() {
const res = spawnSync("git", ["remote", "get-url", "origin"], { encoding: "utf8" });
if (res.status !== 0) return null;
const url = res.stdout.trim();
let m = url.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/);
return m ? m[1] : null;
}
function getPullRequest() {
return ghJson(["api", `repos/${owner}/${repoName}/pulls/${prNumber}`]).data;
}
async function createReview(payload) {
return ghJson([
"api",
"--method",
"POST",
`repos/${owner}/${repoName}/pulls/${prNumber}/reviews`,
"--input",
"-",
], payload);
}
async function createIssueComment(body) {
return ghJson([
"api",
"--method",
"POST",
`repos/${owner}/${repoName}/issues/${prNumber}/comments`,
"--input",
"-",
], { body });
}
async function listReviews(page, perPage) {
return ghJson(["api", "--method", "GET", `repos/${owner}/${repoName}/pulls/${prNumber}/reviews`, "-F", `per_page=${perPage}`, "-F", `page=${page}`]);
}
async function listReviewComments(page, perPage) {
return ghJson(["api", "--method", "GET", `repos/${owner}/${repoName}/pulls/${prNumber}/comments`, "-F", `per_page=${perPage}`, "-F", `page=${page}`]);
}
async function listIssueComments(page, perPage) {
return ghJson(["api", "--method", "GET", `repos/${owner}/${repoName}/issues/${prNumber}/comments`, "-F", `per_page=${perPage}`, "-F", `page=${page}`]);
}
function ghJson(args, inputObj) {
const env = { ...process.env };
if (process.env.OCR_CLEAR_GH_TOKEN === "1") {
delete env.GITHUB_TOKEN;
delete env.GH_TOKEN;
}
const res = spawnSync("gh", args, {
input: inputObj ? JSON.stringify(inputObj) : undefined,
encoding: "utf8",
env,
});
const stdout = res.stdout || "";
const stderr = res.stderr || "";
if (res.status !== 0) {
const err = new Error((stderr || stdout || `gh exited ${res.status}`).trim());
err.status = parseHttpStatus(stderr + "\n" + stdout);
err.response = { headers: parseGhHeaders(stderr) };
throw err;
}
return { data: stdout.trim() ? JSON.parse(stdout) : null, headers: parseGhHeaders(stderr) };
}
function parseHttpStatus(text) {
const m = String(text || "").match(/\bHTTP\s+(\d{3})\b|status code:\s*(\d{3})/i);
return m ? Number(m[1] || m[2]) : undefined;
}
function parseGhHeaders(_) {
return {};
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function withRetry(tag, fn) {
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
return await fn();
} catch (e) {
const retryInfo = computeRetryDelayMs(e, attempt);
const willRetry = retryInfo != null && attempt < MAX_RETRIES;
if (willRetry) {
console.log(`[${tag}] transient/rate-limited. Waiting ${(retryInfo.delayMs / 1000).toFixed(1)}s via ${retryInfo.source}. ${e.message}`);
await sleep(retryInfo.delayMs);
} else {
throw e;
}
}
}
}
async function readWithPacing(tag, fn) {
const res = await withRetry(tag, fn);
const remaining = logRateLimitQuota(res, tag);
if (remaining != null && remaining <= LOW_REMAINING_THRESHOLD) {
await sleep(READ_LOW_REMAINING_SPACING);
} else {
await sleep(READ_SUCCESS_DELAY);
}
return res;
}
async function readAllPages(tag, pageFn, maxPages = 50) {
const all = [];
let page = 1;
const perPage = 100;
while (page <= maxPages) {
const res = await readWithPacing(`${tag} page ${page}`, () => pageFn(page, perPage));
const items = res.data || [];
all.push(...items);
if (items.length < perPage) break;
page++;
}
if (page > maxPages) {
console.log(`[${tag}] reached max page limit (${maxPages}); results may be incomplete.`);
}
return all;
}
async function findExistingBatchReview({ tag }) {
const reviews = await readAllPages("listReviews", listReviews);
for (const r of reviews) {
if ((r.body || "").includes(tag)) {
return { found: true, review: r };
}
}
return { found: false };
}
async function getPostedCommentIds() {
const comments = await readAllPages("listReviewComments", listReviewComments);
const ids = new Set();
const idRe = /<!--\s*(ocr-[A-Za-z0-9_.-]+-[A-Za-z0-9_.-]+-[a-f0-9]+)\s*-->/g;
for (const c of comments) {
const body = c.body || "";
let m;
while ((m = idRe.exec(body)) !== null) ids.add(m[1]);
}
return ids;
}
async function isCommentAlreadyPosted({ id }) {
try {
const posted = await getPostedCommentIds();
return posted.has(id);
} catch (e) {
console.log(`[isCommentAlreadyPosted] check failed for ${id}: ${e.message}`);
return null;
}
}
async function hasIssueCommentWithId({ id }) {
try {
const comments = await readAllPages("listIssueComments", listIssueComments);
return comments.some((c) => (c.body || "").includes(id));
} catch (e) {
console.log(`[listIssueComments] check failed: ${e.message}`);
return null;
}
}
function parseNonNegInt(val, defaultVal) {
const n = parseInt(val, 10);
return Number.isFinite(n) && n >= 0 ? n : defaultVal;
}
function getHeader(headers, name) {
const v = headers[name] != null ? headers[name] : headers[name.toLowerCase()];
return v != null ? String(v).trim() : undefined;
}
function computeRetryDelayMs(error, attempt) {
if (!error) return null;
const status = error.status;
const message = String(error.message || "");
const isRateLimit = status === 429 || (status === 403 && /rate limit|abuse|secondary/i.test(message));
const isTransient = (status >= 500 && status < 600) || status === 408;
if (!isRateLimit && !isTransient) return null;
const headers = ((error.response || {}).headers) || {};
const header = (name) => getHeader(headers, name);
const nowSec = Math.floor(Date.now() / 1000);
const cap = parseInt(process.env.OCR_RETRY_MAX_DELAY, 10) || 300000;
const base = parseInt(process.env.OCR_RETRY_BASE_DELAY, 10) || 60000;
let info = null;
if (isRateLimit) {
const retryAfter = header("retry-after");
if (retryAfter) {
const secs = Number(retryAfter);
if (!isNaN(secs) && secs >= 0) {
info = { rawMs: secs * 1000, source: "retry-after", detail: `${secs}s` };
} else {
const dateMs = Date.parse(retryAfter);
if (!isNaN(dateMs)) {
info = { rawMs: Math.max(0, dateMs - Date.now()), source: "retry-after-date", detail: retryAfter };
}
}
}
if (!info) {
const remaining = header("x-ratelimit-remaining");
const reset = header("x-ratelimit-reset");
if (reset != null && Number(remaining) === 0) {
const rawMs = Math.max(0, Number(reset) - nowSec) * 1000;
info = { rawMs, source: "x-ratelimit-reset", detail: `reset epoch=${reset}` };
}
}
if (!info) {
const jitter = Math.floor(Math.random() * 1000);
info = { rawMs: Math.min(base * Math.pow(2, attempt), cap) + jitter, source: "exponential-backoff", detail: `attempt=${attempt}` };
}
} else {
const transientBase = 2000;
const jitter = Math.floor(Math.random() * 1000);
info = { rawMs: Math.min(transientBase * Math.pow(2, attempt), cap) + jitter, source: "transient-backoff", detail: `HTTP ${status}` };
}
const delayMs = Math.min(info.rawMs, cap);
return { delayMs, source: info.source, detail: info.detail };
}
function logRateLimitQuota(response, tag) {
try {
const h = (response && response.headers) || {};
const remaining = getHeader(h, "x-ratelimit-remaining");
const limit = getHeader(h, "x-ratelimit-limit");
const reset = getHeader(h, "x-ratelimit-reset");
if (remaining != null) {
console.log(`[rate-limit] ${tag}: remaining=${remaining}/${limit || "?"}${reset ? `, reset epoch=${reset}` : ""}`);
}
return remaining != null ? Number(remaining) : null;
} catch (_) {
return null;
}
}
function newCommentId() {
return `ocr-${RUN_TAG}-${crypto.randomBytes(8).toString("hex")}`;
}
function resolveLines(comment) {
const start = comment.start_line;
const end = comment.end_line;
if (start >= 1 && end >= 1 && start !== end) {
return { start_line: start, line: end, start_side: "RIGHT", side: "RIGHT" };
} else if (end >= 1) {
return { line: end, side: "RIGHT" };
} else if (start >= 1) {
return { line: start, side: "RIGHT" };
}
return {};
}
function toReviewPayload(item) {
return {
path: item.comment.path,
body: buildBody(item.comment, item.id),
...item.lines,
};
}
function buildBody(comment, id) {
let body = `<!-- ${id} -->\n`;
body += comment.content || "";
if (comment.suggestion_code && comment.existing_code) {
body += "\n\nSuggestion:\n";
body += fencedBlock(comment.suggestion_code, "suggestion");
}
return body;
}
function formatCommentMarkdown(comment, error) {
let md = `### \`${comment.path}\``;
if (comment.start_line && comment.end_line) {
md += ` (L${comment.start_line}-L${comment.end_line})`;
}
md += "\n\n";
if (error) {
md += `GitHub could not post this as an inline comment: ${error}\n\n`;
}
md += comment.content || "";
if (comment.suggestion_code && comment.existing_code) {
md += "\n\n<details><summary>Suggested Change</summary>\n\n";
md += "Before:\n" + fencedBlock(comment.existing_code) + "\n\n";
md += "After:\n" + fencedBlock(comment.suggestion_code) + "\n\n";
md += "</details>";
}
return md;
}
function buildSummaryBody(totalCount, inlineCount, summaryCount, warnings) {
let body = `OpenCodeReview found ${totalCount} issue(s) in this PR.`;
if (totalCount > 0) {
body += `\n- ${inlineCount} posted as inline comment(s)`;
body += `\n- ${summaryCount} posted as summary`;
}
if (warnings.length > 0) {
body += `\n\n${warnings.length} warning(s) occurred during review.`;
}
return body;
}
function formatSummaryComments(summaryComments) {
let body = "";
for (const { comment } of summaryComments) {
body += "\n\n---\n\n";
body += formatCommentMarkdown(comment);
}
return body;
}
function fencedBlock(content, language = "") {
const text = String(content || "");
const fence = safeFence(text);
let block = fence + language + "\n" + text;
if (!text.endsWith("\n")) block += "\n";
return block + fence;
}
function safeFence(content) {
const matches = String(content || "").match(/`+/g) || [];
const maxTicks = matches.reduce((max, ticks) => Math.max(max, ticks.length), 0);
return "`".repeat(Math.max(3, maxTicks + 1));
}
function readIfExists(path) {
try {
return fs.readFileSync(path, "utf8");
} catch (_) {
return "";
}
}
function cleanRunPart(value) {
return String(value || "local").replace(/[^A-Za-z0-9_.-]/g, "_");
}
function fail(message) {
console.error(`error: ${message}`);
console.error("Run with --help for usage.");
process.exit(2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment