Skip to content

Commit d7eb5e6

Browse files
authored
Treat Copilot CLI's own retry-exhaustion message as non-retryable in harness classification (#53569)
1 parent 7a37560 commit d7eb5e6

5 files changed

Lines changed: 70 additions & 10 deletions

File tree

.github/workflows/daily-team-evolution-insights.lock.yml

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/mcp-inspector.lock.yml

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

actions/setup/js/copilot_harness.test.cjs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,33 @@ describe("copilot_harness.cjs", () => {
170170
expect(isCAPIQuotaExceededError("Authentication failed")).toBe(false);
171171
expect(isCAPIQuotaExceededError("")).toBe(false);
172172
});
173+
174+
it("matches the Copilot CLI's own retry-exhaustion message without a CAPIError: prefix (429)", () => {
175+
const output =
176+
"Failed to get response from the AI model; retried 5 times (total retry wait time: 380.35 seconds) " +
177+
"(Request-ID AC21:F5CEC:33A719:40DD88:6A83AA27) Last error: 429 Too Many Requests\nChanges +0 -0";
178+
expect(isCAPIQuotaExceededError(output)).toBe(true);
179+
});
180+
181+
it("matches the Copilot CLI's own retry-exhaustion message for 5xx statuses (503)", () => {
182+
const output = "Failed to get response from the AI model; retried 5 times (total retry wait time: 300 seconds) Last error: 503 Service Unavailable";
183+
expect(isCAPIQuotaExceededError(output)).toBe(true);
184+
});
185+
186+
it("does not retry a zero-progress attempt that exhausted the CLI's own 429 retries", () => {
187+
const output =
188+
"Failed to get response from the AI model; retried 5 times (total retry wait time: 380.35 seconds) " +
189+
"(Request-ID AC21:F5CEC:33A719:40DD88:6A83AA27) Last error: 429 Too Many Requests";
190+
expect(
191+
shouldRetryFailedExecution({
192+
exitCode: 1,
193+
hasOutput: true,
194+
output,
195+
attempt: 0,
196+
maxRetries: 3,
197+
})
198+
).toBe(false);
199+
});
173200
});
174201

175202
it("matches CAPIError: 400 with various spacing", () => {

actions/setup/js/detect_agent_errors.cjs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
* response (for example "Response status code does not indicate success: 400 (Bad Request)").
2626
* - capi_quota_exceeded_error: The Copilot CAPI quota has been exhausted
2727
* or rate-limited (e.g., "CAPIError: 429 429 quota exceeded",
28-
* "CAPIError: Too Many Requests"). All matched forms are treated as
29-
* non-retryable because the Copilot SDK has already retried internally
30-
* before surfacing the error.
28+
* "CAPIError: Too Many Requests", or the Copilot CLI's own
29+
* retry-exhaustion message "Failed to get response from the AI model;
30+
* retried N times ... Last error: 429/5xx" which carries no "CAPIError:"
31+
* prefix). All matched forms are treated as non-retryable because the
32+
* Copilot CLI/SDK has already retried internally before surfacing the
33+
* error.
3134
* - invocation_cap_exceeded: The per-run pooled LLM invocation cap is
3235
* fully exhausted (e.g., "CAPIError: 429 Maximum LLM invocations exceeded (N/N)"
3336
* or `"type":"max_runs_exceeded"`). This is more specific than generic
@@ -165,9 +168,13 @@ const MISSING_MODEL_PRICING_PATTERN = /Model\s+"([^"]+)"\s+has no AI credits pri
165168
// "CAPIError: 429 429 quota exceeded" (original observed form)
166169
// "CAPIError: 429 Too Many Requests" (HTTP 429 form)
167170
// "CAPIError: Too Many Requests" (no status code in message)
168-
// All forms are treated as non-retryable; the Copilot SDK has already retried
169-
// internally before surfacing this error (evidenced by "retried 5 times" context).
170-
const CAPI_QUOTA_EXCEEDED_PATTERN = /CAPIError:\s*(?:429\s+)?(?:429\s+quota exceeded|Too Many Requests)/i;
171+
// "Failed to get response from the AI model; retried 5 times ... Last error: 429 Too Many Requests"
172+
// (Copilot CLI's own retry-exhaustion message, no "CAPIError:" prefix — seen with both
173+
// 429 and 5xx terminal statuses, e.g. "Last error: 503 Service Unavailable")
174+
// All forms are treated as non-retryable; the Copilot CLI/SDK has already retried
175+
// internally before surfacing this error (evidenced by "retried N times" context).
176+
const CAPI_QUOTA_EXCEEDED_PATTERN =
177+
/CAPIError:\s*(?:429\s+)?(?:429\s+quota exceeded|Too Many Requests)|Failed to get response from the AI model;\s*retried\s+\d+\s+times[^\n]{0,300}?Last error:\s*(?:429|5\d{2})\b/i;
171178

172179
/**
173180
* Build a case-insensitive merged RegExp from literal/regex patterns.

actions/setup/js/detect_agent_errors.test.cjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,22 @@ describe("detect_agent_errors.cjs", () => {
221221
// it should NOT match the CAPI quota pattern.
222222
expect(isCAPIQuotaExceededError("CAPIError: 429 Maximum LLM invocations exceeded (25/25)")).toBe(false);
223223
});
224+
225+
it("matches the Copilot CLI's own retry-exhaustion message with no CAPIError: prefix (429)", () => {
226+
const message =
227+
"Failed to get response from the AI model; retried 5 times (total retry wait time: 380.35 seconds) " +
228+
"(Request-ID AC21:F5CEC:33A719:40DD88:6A83AA27) Last error: 429 Too Many Requests";
229+
expect(isCAPIQuotaExceededError(message)).toBe(true);
230+
});
231+
232+
it("matches the Copilot CLI's own retry-exhaustion message for 5xx statuses (503)", () => {
233+
const message = "Failed to get response from the AI model; retried 5 times (total retry wait time: 300 seconds) Last error: 503 Service Unavailable";
234+
expect(isCAPIQuotaExceededError(message)).toBe(true);
235+
});
236+
237+
it("does not match a 'Failed to get response' message without retry-exhaustion context", () => {
238+
expect(isCAPIQuotaExceededError("Failed to get response from the AI model due to a network error")).toBe(false);
239+
});
224240
});
225241

226242
describe("INVOCATION_CAP_EXCEEDED_PATTERN / isInvocationCapExceededError", () => {

0 commit comments

Comments
 (0)