Repository navigation
Replies: 1 comment
|
Adding a production observation on Windows with DeepSeek Harness 0.2.1-rc.2, using the built-in llm-pi-ai adapter and openai-codex route. Two retry events occurred during one call on 2026-10-18 (UTC):
The error message included an HTML fragment, but the inspected failure records had no structured HTTP status or requestId. These observations do not establish that the actual response status was 5xx, identify the responding intermediary, or prove the underlying cause. This appears related to the metadata-loss issue described here. Current public source in packages/llm/llm-pi-ai/src/stream.ts constructs the ordinary error failure from message and a text-derived code; classifyPiAiError checks /\b5\d\d\b/ in that text. Source: https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/llm/llm-pi-ai/src/stream.ts An unrelated three-digit 5xx-looking number inside an HTML body could match this rule. This is a potential false-positive mechanism, not a demonstrated explanation for these two retries. Could the adapter preserve allowlisted response metadata per attempt (actual HTTP status, sanitized request identifier, and retry hints when available), while distinguishing structured status classification from text fallback? A regression test with HTML containing unrelated 5xx-looking numbers would also help. As noted above, onResponse availability differs across pi-ai routes, so this should be verified for the specific openai-codex implementation rather than assumed to work uniformly. This report intentionally omits raw response bodies, conversation content, account identifiers, credentials, session IDs, and local paths. |
Uh oh!
There was an error while loading. Please reload this page.
Two provider statements about a failed request do not survive to the layer that decides whether to
retry: the requested delay (
Retry-After) and the provider's retry directive (x-should-retry).Measured with a local stub as the route's
baseURL, so the gateway is deterministic and no livecredential is involved:
429RATE_LIMIT429+x-should-retry: falseRATE_LIMIT429+retry-after: 30RATE_LIMIT429+retry-after-ms: 750, and the HTTP-date formRATE_LIMITBoth facts exist on the SDK error object inside pi-ai, and the same file reads them when pi-ai's own
retry loop runs. The loop does not run here, which is the intended layering, so the facts are read by
nobody.
Where the two facts die
pi-ai 1.0.4, all three wires the harness uses (
openai-completions,anthropic-messages,openai-responses):packages/ai/src/utils/error-body.ts—normalizeProviderErrorprobes four SDK field positions forthe status and extracts a body, then returns
{status, body, message, messageCarriesBody}.error.headersis never read.packages/ai/src/api/openai-completions.ts:719— the error reaches the assistant message only aserrorMessage = formatProviderError(normalizeProviderError(error)). A string is the only carrier.onResponseinStreamOptionslooks like the seam, but on these wires it is invoked after theawaited SDK call returns (
openai-completions.ts:383,anthropic-messages.ts:657,openai-responses.ts:191), so a 429 that throws never reaches it. pi-ai's own codex wire, whichuses raw fetch, calls
onResponsebefore checkingresponse.ok(
openai-codex-responses.ts:414), so the package is inconsistent with itself here.retryProviderRequestdefaults tomaxRetries: 0and documents that callers must run the SDK withmaxRetries: 0when a higher layer owns retrying.DeepSeek Harness side:
packages/llm/llm-pi-ai/src/adapter.ts:130passesmaxRetries: 0, matching that layering.packages/llm/llm-pi-ai/src/stream.ts:125builds the failure as{ message: text, code: classifyPiAiError(text) }— nostatus, noproviderRetryAfterMs, andclassifyPiAiErrorpattern-matches the flattened string, with anXXX(pi-ai upstream)comment aboveit that already records why.
packages/llm/llm-pi-ai/src/config.ts:380removedmaxRetriesandmaxRetryDelayMsfrom routeconfig, so an operator cannot opt back into pi-ai's loop either.
The harness already knows how to treat these facts
packages/llm/llm-deepseek/src/transport.tstakes the responseHeadersfor the native DeepSeekroute, parses
retry-afterin both forms, and setsstatus,requestIdandproviderRetryAfterMson the failure.
packages/llm/llm-retry/src/index.ts:227consumesproviderRetryAfterMs: a value notgreater than
maxDelayMsis honoured verbatim, above itnormalmode gives up andalwayssubstitutes local backoff. That receiving half works.
So the vocabulary and the treatment exist. Only the pi-ai path cannot fill them, because the headers
are discarded below it.
Request
Add the orthogonal half of the vocabulary to
LlmFailure, and let policy consume it:The union rather than a boolean, so that absence keeps meaning "the provider made no statement".
Handling deliberately asymmetric:
Honouring a negative directive can only reduce load. Honouring a positive one unconditionally would
hand a provider control over the retry budget, so it stays bounded.
This is a no-op until the transport preserves the facts, which is why it is worth deciding separately
from implementing it.
Status of the transport prerequisite
The pi-ai side has been reported twice and declined twice, both
no-action:#9595 (agent-level retry ignores Retry-After; the
body already names
normalizeProviderErrorand the missing assistant-message field) and#8507, which carries a downstream comment from a
DeepSeek Harness maintainer making the same argument. A third report of the same class would be noise
on their tracker, so none was filed.
That is the point of raising it here: the fact is currently unobtainable from the pi-ai route, and no
configuration change on this side can recover it.
Measurements come from a local probe harness against a stub gateway, never a live credential; scripts
can be shared on request. Draft written with agent assistance from those measurements.
All reactions