action: retry transient GitHub 422 "internal error" on review submission (#610)
* action: retry transient GitHub 422 "internal error" on review submission
GitHub sometimes 422s POST /pulls/{n}/reviews with body
"An internal error occurred, please try again." — a server-side hiccup
that the existing 422 handler framed with the generic
"likely causes (1)(2)(3)" prompt listing affected comments. the agent
dutifully refetched the diff, dropped comments, and resubmitted, hitting
the same transient error on a shifting affected-comments list until
GitHub accepted. some runs logged 8+ spurious retries with ~11 minutes
of wall-clock, dropping valid inline comments along the way.
detect the transient body explicitly, retry in-tool twice with 1s/3s
backoff, and surface a distinct error on exhaustion that tells the agent
this is a GitHub-side issue — do not modify inline comments, wait and
retry or fall back to a body-only review. closes #584.
* action: use retry util for transient review 422, drop isTransientReviewError tests
---------
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
4101df566b
commit
851e49e2d7
+14
-3
@@ -4,6 +4,12 @@ import { log } from "./cli.ts";
|
||||
export type RetryOptions = {
|
||||
maxAttempts?: number;
|
||||
delayMs?: number;
|
||||
/**
|
||||
* explicit delay schedule — one entry per retry (length N ⇒ N+1 attempts).
|
||||
* when set, overrides `maxAttempts` and `delayMs`. e.g. `[1_000, 3_000]`
|
||||
* means up to 3 attempts, sleeping 1s before retry 2 and 3s before retry 3.
|
||||
*/
|
||||
delaysMs?: readonly number[];
|
||||
shouldRetry?: (error: unknown) => boolean;
|
||||
label?: string;
|
||||
};
|
||||
@@ -20,10 +26,15 @@ const defaultShouldRetry = (error: unknown): boolean => {
|
||||
};
|
||||
|
||||
export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
|
||||
const maxAttempts = options.maxAttempts ?? 3;
|
||||
const delayMs = options.delayMs ?? 1000;
|
||||
const shouldRetry = options.shouldRetry ?? defaultShouldRetry;
|
||||
const label = options.label ?? "operation";
|
||||
const delays = options.delaysMs
|
||||
? Array.from(options.delaysMs)
|
||||
: Array.from(
|
||||
{ length: (options.maxAttempts ?? 3) - 1 },
|
||||
(_, i) => (options.delayMs ?? 1000) * (i + 1)
|
||||
);
|
||||
const maxAttempts = delays.length + 1;
|
||||
|
||||
let lastError: unknown;
|
||||
|
||||
@@ -37,7 +48,7 @@ export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {})
|
||||
throw error;
|
||||
}
|
||||
|
||||
const delay = delayMs * attempt;
|
||||
const delay = delays[attempt - 1]!;
|
||||
log.info(`» ${label} failed (attempt ${attempt}/${maxAttempts}), retrying in ${delay}ms...`);
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user