Repository navigation
[Bug] 未分类的空 body HTTP 400 被判为 INVALID_REQUEST,导致长会话被永久锁死 #9352
Replies: 2 comments
|
Your report is accurate on every point, and the mechanism is visible in the source — including one design fact you cannot see from the outside that explains why a bodyless 400 lands in the "the request is invalid" bucket, and why that bucket is the wrong one. The classification, and the evidence it rests on
// line 48 — and this one IS justified, by the comment above it:
// "A rejected request body (gateway or provider size cap): resending the
// same request cannot succeed, so it is invalid, not transient."
if (/\b413\b|failed to buffer the request body:…|payload too large|…/i.test(message)) return 'INVALID_REQUEST'
if (/\b400\b|invalid.?request/i.test(message)) return 'INVALID_REQUEST' // line 49Note the asymmetry: the That heuristic is not an oversight — the module documents it (
So the text-matching is a documented workaround for an upstream flattening, and your case is its signature failure mode: the one input where there is no text to match on, only a status code. Your Why the session could not self-heal (three independent gates)
Together those make the failure absorbing: the state that triggers it is the state that persists, and nothing in the path removes it. Your replay evidence (the identical request returning 200 twice afterwards) is what makes this a robustness defect rather than a size limit — and it is the right evidence to have collected. One thing worth adding to the report: the harness already has the concept you are asking for on the success path. A workaround you can apply today
providers:
opencode-go: # the profile in your report
retryPolicy:
mode: normal
retryableCodes: [EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT, INVALID_REQUEST]Two caveats, both real: the list is a replacement, not a merge, so the defaults have to be re-listed or you silently lose them; and it is blunt — it makes every The plugin formThis is the same over-broad-classification shape as the zero-output case, so it already has a packaged form one code over: The predicate that would cover it is structural and needs no guesswork: a terminal error frame whose code is Both halves of the upstream ask in your report are correct, and they are separable: the diagnosis half (persist status, headers and a body snippet so a reader can tell a gateway blip from a rejected request) is pure loss today and worth fixing on its own; the classification half is the behaviour change above. Your TL;DR's framing — a transient provider-side failure amplified into a permanent one — is the right way to describe it, and the |
|
What changedSame seam as before — the public
The second one is gated on the message ( Anchored at the end, because when pi-ai writes a provider prefix it comes first ( What it produces, for your frame: // before
{ type: 'finish', reason: { kind: 'error', failure: {
code: 'INVALID_REQUEST', message: '400 status code (no body)' } } }
// after
{ type: 'finish', reason: { kind: 'error', failure: {
code: 'EMPTY_RESPONSE', message: '400 status code (no body)' } } }Only the code changes: the message, What it does not do — the part of your report it cannot answerBoth halves of your upstream ask are in the report and only one is reachable from a plugin:
One adjacent case is deliberately not touched: for the single provider whose bodyless EvidenceA predicate on text is worth exactly as much as what it is tested against, so the suite derives its fixtures from the installed stack rather than transcribing them:
The suite passes on all three lines the peer range admits ( Config, if you want to narrow or widen it for your gateway: plugins:
empty-response-retry:
module: '@argszero/cordis-plugin-empty-response-retry'
config:
mode: warn # log first, don't change behaviour yet
statusOnlyCodes: [INVALID_REQUEST]
# statusOnlyMessages: ['\b\d{3}\s+status\s+code\s+\(no body\)$']
The upstream half is unchangedYour diagnosis is the one that still wants a fix in the tree. The text-matching is documented as a workaround ( |
Uh oh!
There was an error while loading. Please reload this page.
dsh-bug-unclassified-400-INVALID_REQUEST.md
All reactions