Skip to content

Commit d771daa

Browse files
authored
Merge pull request #6778 from JSONbored/fix/review-burst-metrics-noop
fix(review): don't count a byte-identical republish toward review-burst (#6724)
2 parents 9f77ee9 + 92afd44 commit d771daa

4 files changed

Lines changed: 293 additions & 47 deletions

File tree

src/github/comments.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export async function createOrUpdatePrIntelligenceComment(
2626
pullNumber: number,
2727
body: string,
2828
options: { createIfMissing?: boolean | undefined; mode?: AgentActionMode } = {},
29-
): Promise<{ id: number; html_url?: string } | null> {
29+
): Promise<{ id: number; html_url?: string; changed: boolean } | null> {
3030
return createOrUpdateIssueCommentWithMarker(env, installationId, repoFullName, pullNumber, body, PR_INTELLIGENCE_COMMENT_MARKER, options);
3131
}
3232

@@ -37,10 +37,15 @@ export async function createOrUpdateAgentCommandComment(
3737
issueNumber: number,
3838
body: string,
3939
mode: AgentActionMode = "live",
40-
): Promise<{ id: number; html_url?: string } | null> {
40+
): Promise<{ id: number; html_url?: string; changed: boolean } | null> {
4141
return createOrUpdateIssueCommentWithMarker(env, installationId, repoFullName, issueNumber, body, AGENT_COMMAND_COMMENT_MARKER, { mode });
4242
}
4343

44+
// #6724 (review-burst): `changed` distinguishes a genuine no-op (the rendered body was byte-identical to what's
45+
// already posted, PATCH skipped -- see the idempotency comment below) from a real create/update, so a caller can
46+
// avoid double-counting a republish that produced no visible change. `false` ONLY on the proven-identical path;
47+
// every other return (created, updated, or `createIfMissing: false` returning null) is `true`/absent because
48+
// there's no cheap, safe way to prove those didn't change anything.
4449
async function createOrUpdateIssueCommentWithMarker(
4550
env: Env,
4651
installationId: number,
@@ -49,7 +54,7 @@ async function createOrUpdateIssueCommentWithMarker(
4954
body: string,
5055
marker: string,
5156
options: { createIfMissing?: boolean | undefined; mode?: AgentActionMode } = {},
52-
): Promise<{ id: number; html_url?: string } | null> {
57+
): Promise<{ id: number; html_url?: string; changed: boolean } | null> {
5358
const parts = repoFullName.split("/");
5459
const owner = parts[0];
5560
const repo = parts[1];
@@ -84,7 +89,7 @@ async function createOrUpdateIssueCommentWithMarker(
8489
// marker — also collapses a duplicate webhook delivery for the same commit.
8590
if (canonical.body === body) {
8691
await deleteDuplicateMarkerComments(octokit, owner, repo, existing, canonical.id);
87-
return { id: canonical.id, ...(canonical.html_url !== undefined ? { html_url: canonical.html_url } : {}) };
92+
return { id: canonical.id, ...(canonical.html_url !== undefined ? { html_url: canonical.html_url } : {}), changed: false };
8893
}
8994
const response = await octokit.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", {
9095
owner,
@@ -93,7 +98,7 @@ async function createOrUpdateIssueCommentWithMarker(
9398
body,
9499
});
95100
await deleteDuplicateMarkerComments(octokit, owner, repo, existing, canonical.id);
96-
return response.data as { id: number; html_url?: string };
101+
return { ...(response.data as { id: number; html_url?: string }), changed: true };
97102
}
98103
if (options.createIfMissing === false) return null;
99104
const response = await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
@@ -102,7 +107,7 @@ async function createOrUpdateIssueCommentWithMarker(
102107
issue_number: issueNumber,
103108
body,
104109
});
105-
return response.data as { id: number; html_url?: string };
110+
return { ...(response.data as { id: number; html_url?: string }), changed: true };
106111
});
107112
}
108113

src/queue/processors.ts

Lines changed: 83 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8303,6 +8303,12 @@ async function maybePublishPrPublicSurface(
83038303
let autoReviewSkipReasonForPublish: string | null = null;
83048304
const publishedOutputs: PublicSurfaceOutput[] = [];
83058305
const failedOutputs: PublicSurfaceOutputFailure[] = [];
8306+
// #6724 (review-burst): true until a comment/label publish proves itself a no-op (byte-identical body / label
8307+
// already present) -- read by finishPublicSurfacePublication's final call below to avoid double-counting a
8308+
// republish that changed nothing on GitHub. Stay true (the safe default) for any output this pass doesn't
8309+
// attempt, and for output kinds with no cheap no-op signal (gate_check_run, check_run).
8310+
let commentContentChanged = true;
8311+
let labelContentChanged = true;
83068312
const reviewedHeadSha = reviewedPullRequestHeadSha(pr.headSha, advisory.headSha);
83078313
const freshnessForReviewOutput = (phase: string): Promise<PullRequestFreshness> =>
83088314
reviewTargetFreshness(env, {
@@ -8352,7 +8358,10 @@ async function maybePublishPrPublicSurface(
83528358
});
83538359
return reviewFiles;
83548360
};
8355-
const finishPublicSurfacePublication = async (): Promise<
8361+
// #6724 (review-burst): contentChanged defaults true so every existing caller (the three early-return call
8362+
// sites below, none of which reach the comment/label publish steps) keeps recording pr_public_surface_published
8363+
// exactly as before -- only the final "full" call site computes and passes the real value.
8364+
const finishPublicSurfacePublication = async (contentChanged = true): Promise<
83568365
ReturnType<typeof evaluateGateCheck> | undefined
83578366
> => {
83588367
const gateSurfaceIncomplete = gateEnabled && !gateFinalized;
@@ -8467,41 +8476,61 @@ async function maybePublishPrPublicSurface(
84678476
.then((startedAt) => reviewDurationMsSince(startedAt, Date.now()))
84688477
.catch(() => undefined)
84698478
: undefined;
8470-
await recordAuditEvent(env, {
8471-
eventType: "github_app.pr_public_surface_published",
8472-
actor: author,
8473-
targetKey: `${repoFullName}#${pr.number}`,
8474-
outcome: "completed",
8475-
metadata: {
8476-
deliveryId: webhook.deliveryId,
8477-
publicSurface: settings.publicSurface,
8478-
label: decision.willLabel ? settings.gittensorLabel : null,
8479-
checkRunMode: settings.checkRunMode,
8480-
reviewCheckMode: settings.reviewCheckMode,
8481-
publicAudienceMode: settings.publicAudienceMode,
8482-
publishedOutputs,
8483-
failedOutputs,
8484-
gateCheckFinalized: gateFinalized,
8485-
...(reviewEffortMinutesForStats !== undefined ? { reviewEffortMinutes: reviewEffortMinutesForStats } : {}),
8486-
...(reviewDurationMsForStats !== undefined ? { reviewDurationMs: reviewDurationMsForStats } : {}),
8487-
},
8488-
});
8489-
await recordGithubProductUsage(env, "pr_public_surface_published", {
8490-
actor: author,
8491-
repoFullName,
8492-
targetKey: `${repoFullName}#${pr.number}`,
8493-
outcome: "completed",
8494-
metadata: {
8495-
publicSurface: settings.publicSurface,
8496-
labelApplied: decision.willLabel,
8497-
checkRunMode: settings.checkRunMode,
8498-
reviewCheckMode: settings.reviewCheckMode,
8499-
publicAudienceMode: settings.publicAudienceMode,
8500-
publishedOutputs,
8501-
failedOutputs,
8502-
gateCheckFinalized: gateFinalized,
8503-
},
8504-
});
8479+
// #6724 (review-burst): a proven no-op republish (comment byte-identical, label already present) still
8480+
// completes every side effect below (surface-published stamp, AI-review-published stamp) -- those ARE
8481+
// accurate on a no-op, since the head genuinely is current -- but skips the two DURABLE "a review was
8482+
// published" records (this event feeds public-stats.ts/services/public-review-volume-trend.ts and is
8483+
// exempt from retention pruning, db/retention.ts), so a CI-flap/retry-storm that keeps re-rendering the
8484+
// same content stops inflating both the public "reviews completed" count and the review-burst anomaly
8485+
// counter (findHottestReviewTargetForRepo, db/repositories.ts) that reads this exact event type. A narrower
8486+
// event records the no-op instead, mirroring the existing github_app.public_surface_publish_skipped_current
8487+
// precedent (#6685) for the same "provably nothing changed" shape.
8488+
if (contentChanged) {
8489+
await recordAuditEvent(env, {
8490+
eventType: "github_app.pr_public_surface_published",
8491+
actor: author,
8492+
targetKey: `${repoFullName}#${pr.number}`,
8493+
outcome: "completed",
8494+
metadata: {
8495+
deliveryId: webhook.deliveryId,
8496+
publicSurface: settings.publicSurface,
8497+
label: decision.willLabel ? settings.gittensorLabel : null,
8498+
checkRunMode: settings.checkRunMode,
8499+
reviewCheckMode: settings.reviewCheckMode,
8500+
publicAudienceMode: settings.publicAudienceMode,
8501+
publishedOutputs,
8502+
failedOutputs,
8503+
gateCheckFinalized: gateFinalized,
8504+
...(reviewEffortMinutesForStats !== undefined ? { reviewEffortMinutes: reviewEffortMinutesForStats } : {}),
8505+
...(reviewDurationMsForStats !== undefined ? { reviewDurationMs: reviewDurationMsForStats } : {}),
8506+
},
8507+
});
8508+
await recordGithubProductUsage(env, "pr_public_surface_published", {
8509+
actor: author,
8510+
repoFullName,
8511+
targetKey: `${repoFullName}#${pr.number}`,
8512+
outcome: "completed",
8513+
metadata: {
8514+
publicSurface: settings.publicSurface,
8515+
labelApplied: decision.willLabel,
8516+
checkRunMode: settings.checkRunMode,
8517+
reviewCheckMode: settings.reviewCheckMode,
8518+
publicAudienceMode: settings.publicAudienceMode,
8519+
publishedOutputs,
8520+
failedOutputs,
8521+
gateCheckFinalized: gateFinalized,
8522+
},
8523+
});
8524+
} else {
8525+
await recordAuditEvent(env, {
8526+
eventType: "github_app.pr_public_surface_republish_noop",
8527+
actor: author,
8528+
targetKey: `${repoFullName}#${pr.number}`,
8529+
outcome: "completed",
8530+
detail: "republish produced no visible change (comment byte-identical / label already present)",
8531+
metadata: { deliveryId: webhook.deliveryId, repoFullName, publishedOutputs },
8532+
}).catch(() => undefined);
8533+
}
85058534
// Stamp the head SHA only after every required public surface for this repo completed. For gate-enabled repos,
85068535
// a comment/label without a finalized Orb gate check is incomplete and must stay repair-visible to the sweep.
85078536
await markPullRequestSurfacePublished(env, repoFullName, pr.number, advisory.headSha).catch((error) => {
@@ -10551,7 +10580,7 @@ async function maybePublishPrPublicSurface(
1055110580
});
1055210581
}
1055310582
try {
10554-
await withReviewPipelineSpan(
10583+
const commentResult = await withReviewPipelineSpan(
1055510584
"selfhost.review.publish.comment",
1055610585
{
1055710586
installationId,
@@ -10570,6 +10599,10 @@ async function maybePublishPrPublicSurface(
1057010599
{ mode },
1057110600
),
1057210601
);
10602+
// #6724 (review-burst): only a proven byte-identical no-op (createOrUpdatePrIntelligenceComment's own
10603+
// idempotency check) sets this false -- a real create/update, or the createIfMissing:false null case
10604+
// (not reachable on this call site, which never sets that option), both default true.
10605+
commentContentChanged = commentResult?.changed ?? true;
1057310606
publishedOutputs.push("comment");
1057410607
incr("loopover_reviews_published_total", { repo: repoFullName });
1057510608
// Real end-to-end review latency (#review-latency-metric): pr.headShaObservedAt is the moment THIS
@@ -10616,7 +10649,7 @@ async function maybePublishPrPublicSurface(
1061610649
}
1061710650
if (decision.willLabel) {
1061810651
try {
10619-
await withReviewPipelineSpan(
10652+
const labelResult = await withReviewPipelineSpan(
1062010653
"selfhost.review.publish.label",
1062110654
{
1062210655
installationId,
@@ -10638,6 +10671,9 @@ async function maybePublishPrPublicSurface(
1063810671
},
1063910672
),
1064010673
);
10674+
// #6724 (review-burst): applied:false means the label was already present -- a proven no-op, same idea
10675+
// as commentContentChanged above.
10676+
labelContentChanged = labelResult.applied;
1064110677
publishedOutputs.push("label");
1064210678
} catch (error) {
1064310679
const message = errorMessage(error);
@@ -10654,7 +10690,15 @@ async function maybePublishPrPublicSurface(
1065410690
if (isGitHubRateLimitedError(error)) throw error;
1065510691
}
1065610692
}
10657-
return finishPublicSurfacePublication();
10693+
// #6724 (review-burst): only a PUBLISHED-OUTPUTS SET of exactly comment/label (both proven no-ops, or absent
10694+
// entirely) counts as a no-op pass. Any other output kind in this pass (gate_check_run, check_run) has no
10695+
// cheap no-op signal, so its mere presence keeps this true -- conservative by design, this can only ever
10696+
// suppress a pr_public_surface_published record for a pass PROVEN to have changed nothing, never the reverse.
10697+
const surfaceContentChanged =
10698+
publishedOutputs.some((output) => output !== "comment" && output !== "label") ||
10699+
(publishedOutputs.includes("comment") && commentContentChanged) ||
10700+
(publishedOutputs.includes("label") && labelContentChanged);
10701+
return finishPublicSurfacePublication(surfaceContentChanged);
1065810702
}
1065910703

1066010704
async function recordPublicSurfaceOutputFailure(

test/unit/github-comments.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ describe("GitHub PR intelligence comments", () => {
352352

353353
const result = await createOrUpdatePrIntelligenceComment(createTestEnv({ GITHUB_APP_PRIVATE_KEY: privateKey }), 123, "JSONbored/gittensory", 12, body);
354354

355-
expect(result).toEqual({ id: 101, html_url: "https://github.com/comment/101" }); // html_url-present branch of the early return
355+
expect(result).toEqual({ id: 101, html_url: "https://github.com/comment/101", changed: false }); // html_url-present branch of the early return; changed:false is the #6724 no-op signal
356356
expect(calls.some((call) => call.startsWith("PATCH "))).toBe(false); // identical body → NO GitHub write
357357
});
358358

@@ -372,7 +372,7 @@ describe("GitHub PR intelligence comments", () => {
372372

373373
const result = await createOrUpdatePrIntelligenceComment(createTestEnv({ GITHUB_APP_PRIVATE_KEY: privateKey }), 123, "JSONbored/gittensory", 12, body);
374374

375-
expect(result).toEqual({ id: 202 }); // html_url-absent branch → no html_url key on the early return
375+
expect(result).toEqual({ id: 202, changed: false }); // html_url-absent branch → no html_url key; changed:false is the #6724 no-op signal
376376
expect(calls.some((call) => call.startsWith("PATCH "))).toBe(false);
377377
});
378378

0 commit comments

Comments
 (0)