fix: detect stream_options HTTP 400 as non-retryable Anthropic BYOK error#43127
Conversation
…ropic BYOK Update HTTP_400_RESPONSE_ERROR_PATTERN in copilot_harness.cjs and detect_agent_errors.cjs to match the Copilot SDK error "400 400 400 stream_options: Extra inputs are not permitted". This error is emitted when the SDK sends an OpenAI-only field (stream_options) to an Anthropic-type provider endpoint. Previously the error was not matched, so all 4 retry attempts failed identically and silently (failureClass=partial_execution) instead of stopping immediately with a clear diagnostic. With this fix the harness detects the error as isHTTP400ResponseError, retries once as a fresh run (in case conversation state is stale) and then breaks with "HTTP 400 response error — not retrying". Note: the root-cause (inferProviderTypeForModel always returning "openai" for the "copilot" provider so the OpenAI-format client is used against the Copilot LLM gateway) is already covered by the check at awf_reflect.cjs line 421 and its companion tests in awf_reflect.test.cjs. Closes #43032 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
|
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ Test Quality Sentinel completed test quality analysis. |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #43127 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100). |
There was a problem hiding this comment.
Pull request overview
This PR improves non-retryable error detection for Copilot SDK BYOK runs against Anthropic-format endpoints by recognizing a specific HTTP 400 validation error caused by the OpenAI-only stream_options field, preventing wasted retries and enabling clearer diagnostics.
Changes:
- Extended
HTTP_400_RESPONSE_ERROR_PATTERNin both the harness and action-side detector to match400 ... stream_options: Extra inputs are not permitted. - Added regression tests in
copilot_harness.test.cjsto validate matching (direct + embedded) and to guard against unrelatedstream_optionsmentions.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/detect_agent_errors.cjs | Extends the shared HTTP 400 detection regex to include the stream_options Anthropic BYOK error shape. |
| actions/setup/js/copilot_harness.cjs | Extends the harness-side HTTP 400 detection regex to classify the stream_options error as non-retryable. |
| actions/setup/js/copilot_harness.test.cjs | Adds regression tests covering the new stream_options-related HTTP 400 detection behavior. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Low
| // Pattern: Generic HTTP 400 Bad Request responses emitted by engine / SDK wrappers. | ||
| // NOTE: keep in sync with HTTP_400_RESPONSE_ERROR_PATTERN in copilot_harness.cjs. | ||
| // Also matches "400 400 400 no model endpoints available given user constraints" which is emitted | ||
| // by the Copilot SDK when no model endpoints are available for the user's configured constraints. | ||
| // The second alternative is anchored to a leading "400" to avoid false positives from unrelated | ||
| // Also matches "400 400 400 stream_options: Extra inputs are not permitted" which is emitted when | ||
| // the Copilot SDK sends an OpenAI-only field to an Anthropic-type provider. | ||
| // The non-first alternatives are anchored to a leading "400" to avoid false positives from unrelated | ||
| // diagnostic or informational messages that might contain the phrase. | ||
| const HTTP_400_RESPONSE_ERROR_PATTERN = /(?:Response status code does not indicate success:\s*400(?:\s*\(Bad Request\))?|400[^\n]*no model endpoints available given user constraints)/i; | ||
| const HTTP_400_RESPONSE_ERROR_PATTERN = | ||
| /(?:Response status code does not indicate success:\s*400(?:\s*\(Bad Request\))?|400[^\n]*no model endpoints available given user constraints|400[^\n]*stream_options:\s*Extra inputs are not permitted)/i; |
There was a problem hiding this comment.
Review: fix: detect stream_options HTTP 400 as non-retryable Anthropic BYOK error
Pattern change is correct and safe. Key checks:
- Regex safety — New alternative
400[^\n]*stream_options:\s*Extra inputs are not permitteddoes not match empty string, has no zero-width match risk, and correctly false-negatives on unrelatedstream_optionsmentions. - Sync maintained — Both
copilot_harness.cjsanddetect_agent_errors.cjsupdated identically per the// NOTE: keep in synccontract. - Tests — Three regressions cover exact match, embedded output, and false-positive guard.
- Retry path unchanged — The existing single-retry-then-stop logic already applies to all
isHTTP400ResponseErrorcases; no additional wiring needed. - Comment accuracy — Updated from "second alternative" to "non-first alternatives" to reflect the 3-alternative pattern.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 23.2 AIC · ⌖ 6.06 AIC · ⊞ 4.9K
🧪 Test Quality Sentinel Report✅ Test Quality Score: 80/100 — Excellent
📊 Metrics (3 tests)
Verdict
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnosing-bugs and /tdd — one non-blocking suggestion, otherwise this is a clean, well-structured fix.
📋 Key Themes & Highlights
Key Themes
- Test coverage gap (minor): The three new regression tests were added only to
copilot_harness.test.cjs.detect_agent_errors.test.cjshas its owndescribe("HTTP_400_RESPONSE_ERROR_PATTERN", ...)block that tests the exported symbol directly — it should get the samestream_optionscases so pattern drift is caught at the source.
Positive Highlights
- ✅ Precise root-cause diagnosis — PR description clearly explains the BYOK mismatch, why retries were silently exhausted, and distinguishes the safety-net fix from the upstream
inferProviderTypeForModelmitigation. - ✅ False-positive guard included —
"Configuring stream_options for the request"negative test is exactly the kind of discipline/diagnosing-bugscalls for. - ✅ Pattern kept in sync — both
copilot_harness.cjsanddetect_agent_errors.cjsupdated in lock-step with clearNOTE: keep in synccomments. - ✅ Minimal change — only the regex literal and its comment are touched; no structural churn.
- ✅ Comment quality — the updated comment accurately describes all three alternatives and why the anchoring is there.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 50.9 AIC · ⌖ 5.03 AIC · ⊞ 6.6K
Comment /matt to run again
| // diagnostic or informational messages that might contain the phrase. | ||
| const HTTP_400_RESPONSE_ERROR_PATTERN = /(?:Response status code does not indicate success:\s*400(?:\s*\(Bad Request\))?|400[^\n]*no model endpoints available given user constraints)/i; | ||
| const HTTP_400_RESPONSE_ERROR_PATTERN = | ||
| /(?:Response status code does not indicate success:\s*400(?:\s*\(Bad Request\))?|400[^\n]*no model endpoints available given user constraints|400[^\n]*stream_options:\s*Extra inputs are not permitted)/i; |
There was a problem hiding this comment.
[/tdd] detect_agent_errors.test.cjs directly tests the exported HTTP_400_RESPONSE_ERROR_PATTERN (lines 185–209), but no stream_options cases were added there — only copilot_harness.test.cjs got the new coverage. If the two pattern definitions drift in the future, the dedicated test file won't catch it.
💡 Suggested additions to detect_agent_errors.test.cjs
Add a parallel set of cases inside the existing describe("HTTP_400_RESPONSE_ERROR_PATTERN", ...) block:
it("matches the stream_options: Extra inputs are not permitted Anthropic BYOK error", () => {
expect(
HTTP_400_RESPONSE_ERROR_PATTERN.test(
"[copilot-sdk-driver] [sdk-driver] error: 400 400 400 stream_options: Extra inputs are not permitted"
)
).toBe(true);
});
it("does not false-positive on unrelated messages mentioning stream_options", () => {
expect(
HTTP_400_RESPONSE_ERROR_PATTERN.test("Configuring stream_options for the request")
).toBe(false);
});This mirrors the new cases added to copilot_harness.test.cjs and directly guards the exported symbol against drift.
@copilot please address this.
There was a problem hiding this comment.
Added in ea44e72 — detect_agent_errors.test.cjs now includes both the positive match case and the false-positive guard for stream_options, mirroring the coverage in copilot_harness.test.cjs.
🤖 PR Triage
Score Breakdown: Impact 30 + Urgency 18 + Quality 12 Rationale: Detects HTTP 400 Recommended Action: Fast-track human review; small and well-targeted.
|
|
@copilot run pr-finisher skill |
….cjs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Fixed in f60e097 — ran |
When the Copilot SDK runs in BYOK mode against an Anthropic-format endpoint, it can send the OpenAI-only
stream_optionsfield, causing Anthropic to return400 stream_options: Extra inputs are not permitted. BecauseHTTP_400_RESPONSE_ERROR_PATTERNdidn't match this error shape, the harness silently exhausted all 4 retries withfailureClass=partial_executionand zero agent output.Changes
copilot_harness.cjs+detect_agent_errors.cjs— Add a third alternative toHTTP_400_RESPONSE_ERROR_PATTERNmatching400[^\n]*stream_options:\s*Extra inputs are not permitted. The harness now classifies this asisHTTP400ResponseError, retries once as a fresh run, then breaks with a clear diagnostic instead of burning all retries.copilot_harness.test.cjs— Three regression tests: exact match on the full SDK error string, match embedded in larger output, and a false-positive guard for unrelatedstream_optionsmentions.