fix: fast-fail copilot harness when LLM invocation cap is saturated#45827
Conversation
When a run hits the per-run LLM invocation cap (`CAPIError: 429 Maximum LLM invocations exceeded (N/N)`) the pooled budget is already saturated — subsequent `--continue` retries instantly re-fail with 0B of new output and cannot make progress. Changes: - copilot_harness.cjs: move `nonRetryableGuard` computation before `classifyCopilotFailure` so `isInvocationCapExceeded` is available for classification and the failure log; wire `nonRetryableGuard.maxRunsExceeded` into the guard-check block so the harness breaks out of the retry loop with a clear message instead of continuing to exhausted retries. - classifyCopilotFailure: add `isInvocationCapExceeded` → returns `"invocation_cap_exceeded"` (outranks `capi_quota_exceeded`). - detect_agent_errors.cjs: add `INVOCATION_CAP_EXCEEDED_PATTERN` that matches both the CAPI form and the Anthropic JSON `max_runs_exceeded` form; wire it into `detectErrors()` and `writeOutputs()` as a new `invocation_cap_exceeded` GitHub Actions output variable; update the file-level JSDoc. - Tests: add 13 new test cases covering pattern matching, retry-policy fast-fail for both CAPI and Anthropic forms, `classifyCopilotFailure` classification and precedence, and `detectErrors()` output. Closes #44788 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ssage and clarify test name Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fast-fails Copilot retries when the shared LLM invocation cap is exhausted.
Changes:
- Adds invocation-cap detection and classification.
- Stops retries immediately and improves diagnostics.
- Adds pattern and retry-policy tests.
Show a summary per file
| File | Description |
|---|---|
actions/setup/js/copilot_harness.cjs |
Adds fast-fail guard and classification. |
actions/setup/js/copilot_harness.test.cjs |
Adds retry and classification tests. |
actions/setup/js/detect_agent_errors.cjs |
Detects and emits invocation-cap errors. |
actions/setup/js/detect_agent_errors.test.cjs |
Tests supported error forms and detection results. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Medium
| `model_not_supported_error=${results.modelNotSupportedError}`, | ||
| `http_400_response_error=${results.http400ResponseError}`, | ||
| `capi_quota_exceeded_error=${results.capiQuotaExceededError}`, | ||
| `invocation_cap_exceeded=${results.invocationCapExceeded}`, |
| if (nonRetryableGuard.aiCreditsExceeded) reasons.push("AI credits budget exceeded"); | ||
| if (nonRetryableGuard.awfAPIProxyBlockingRequests) reasons.push("AWF API proxy is blocking requests"); | ||
| if (isInvocationCapExceeded) { | ||
| const budgetMsg = result.hasOutput ? `attempt ${attempt + 1} partial output preserved as run result` : "no output produced"; |
| if (result.exitCode === 0) return false; | ||
| if (hasNumerousPermissionDeniedIssues(result.output)) return false; | ||
| if (isCAPIQuotaExceededError(result.output)) return false; | ||
| if (detectNonRetryableHarnessGuard(result.output).maxRunsExceeded) return false; |
🤖 PR Triage
Score breakdown: Impact 30 + Urgency 22 + Quality 13 Adds fast-fail when LLM invocation cap is hit (429 CAPI), preventing useless retries that waste runner time. JS-layer change with tests. CI passing (17/17). Part of JS runtime fixes batch. Run §29462479491
|
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #45827 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100). |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Test Quality Sentinel completed test quality analysis. |
🧪 Test Quality Sentinel Report
📊 Metrics (10 tests)
|
|
@copilot merge main and recompile |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Merged main (already up to date), recompiled, and fixed the failing CGO test job in |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Fixed in 1084d63. I investigated the failing CGO job logs and updated the stale test expectations to match the current embedded action/container pins ( |
|
@copilot please run the
|
…-on-retry Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Completed a pr-finisher pass on this branch.
I can’t trigger fresh CI from this cloud-agent path; a maintainer re-trigger is still required (close/reopen PR, workflow_dispatch, or maintainer push). |
|
@copilot merge main recompile fix conflicts |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Merged |
When a run exhausts the per-run LLM invocation cap (
CAPIError: 429 Maximum LLM invocations exceeded (N/N)), the pooled budget is shared across all retry attempts in the Execute step. Every--continueretry therefore re-fails instantly with 0B output — the retries are provably useless but the harness kept looping until manually cancelled.Root cause
harness_retry_guard.cjsalready detectedmaxRunsExceededviaMAX_RUNS_EXCEEDED_PATTERNS, butcopilot_harness.cjsnever checked that field — onlyaiCreditsExceededandawfAPIProxyBlockingRequestswere wired into the guard check.Changes
copilot_harness.cjsnonRetryableGuardcomputation beforeclassifyCopilotFailuresoisInvocationCapExceeded = nonRetryableGuard.maxRunsExceededis available for classification and the failure log lineisInvocationCapExceededto the guard-check block — breaks out of the retry loop immediately with a clear message indicating which attempt's partial output is preservedisInvocationCapExceededto the diagnostic log fieldsclassifyCopilotFailureisInvocationCapExceededinput → returns"invocation_cap_exceeded"(takes precedence overcapi_quota_exceeded)detect_agent_errors.cjsINVOCATION_CAP_EXCEEDED_PATTERNmatches both the CAPI form (CAPIError: 429 Maximum LLM invocations exceeded (N/N)) and the Anthropic JSON form ("type":"max_runs_exceeded")detectErrors()andwriteOutputs()as a newinvocation_cap_exceededGitHub Actions output variableTests
classifyCopilotFailureclassification and precedence overcapi_quota_exceeded, anddetectErrors()output integrityBefore: attempt 1 exhausts 25/25 invocations → attempts 2–4 each hit
429 (25/25)with 0B output → job stalls until manually cancelledAfter: attempt 1 exhausts 25/25 → guard fires → harness logs
"LLM invocation cap saturated — pooled per-run budget fully exhausted; retries cannot make progress (attempt 1 partial output preserved as run result)"→ exits immediately