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
9 changes: 7 additions & 2 deletions src/services/ai-e2e-test-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
coerceAiUsage,
estimateNeurons,
isEnabled,
isRateLimitError,
utcDayStartIso,
} from "./ai-review";

Expand Down Expand Up @@ -214,8 +215,12 @@ async function runWorkersE2eTestGen(env: Env, system: string, user: string, maxT
);
const parsed = parseE2eTestGenResponse(coerceAiText(result));
if (parsed) return { testSource: parsed, usage: coerceAiUsage(result) };
} catch {
/* retry / fall through to fallback */
} catch (error) {
// #8672: a 429 will not have cleared by the next attempt a few hundred ms later, so retrying THIS
// model burns the remaining budget for zero additional chance of success -- move straight to the
// fallback model instead (the same guard runWorkersSlopOpinion/runWorkersOpinion already apply).
if (isRateLimitError(error)) break;
/* non-rate-limit error: retry this model / fall through to the fallback model */
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions test/unit/ai-e2e-test-gen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,4 +558,25 @@ describe("runWorkersE2eTestGen (internal)", () => {
const finalFlags = run.mock.calls.map((c) => ((c as unknown[])[1] as { finalAttempt?: boolean }).finalAttempt);
expect(finalFlags).toEqual([false, false, false, false, false, true]);
});

it("breaks to the fallback model immediately on a rate-limit error instead of burning all per-model attempts (#8672)", async () => {
// Every call 429s. Before #8672 this burned all 3 attempts per model (6 calls); now a rate limit short-
// circuits the inner retry loop after the first attempt on each model — 2 models × 1 attempt = 2 calls.
const run = vi.fn(async () => {
throw new Error("workers_ai_http_429");
});
const env = enabledEnv(run);
await expect(runWorkersE2eTestGen(env, "system", "user", 1024)).resolves.toEqual({ testSource: null });
expect(run).toHaveBeenCalledTimes(2);
});

it("still burns all per-model attempts on a NON-rate-limit error (the retry path is unchanged) (#8672)", async () => {
// A transient non-429 error keeps the original behavior: 2 models × 3 attempts = 6 calls.
const run = vi.fn(async () => {
throw new Error("transient_timeout");
});
const env = enabledEnv(run);
await runWorkersE2eTestGen(env, "system", "user", 1024);
expect(run).toHaveBeenCalledTimes(6);
});
});