OpenRouter adapter cannot retry 429 responses, even with `retryConfig` set
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 75/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- ai-infra-agents, backend-api-design
Research direction
The issue is in the OpenRouter adapter's text.ts file, specifically the chatStream, structuredOutput, and structuredOutputStream functions. Look at how the SDK's chat.send method is called; currently only signal and headers are passed. The fix is to add retryCodes to the request options, defaulting to ['5XX'] and allowing override via adapter config. Start by examining the linked SDK code to understand the retryCodes parameter, then modify the adapter to pass it through. Test with the provided reproduction script to ensure 429 responses are retried.
Written by the indexing model from the issue text.
Description
TanStack AI version
0.58.0
Framework/Library version
@tanstack/ai-openrouter: 0.19.17 | @openrouter/sdk: 0.13.20
Describe the bug and the steps to reproduce it
A 429 from OpenRouter is never retried by the OpenRouter adapter. Setting retryConfig on the adapter config does not change that. A 5xx is retried.
The SDK takes the retry strategy from the client (retryConfig), but the list of retryable status codes only from the per-call request options. It defaults to ["5XX"]. There is no client-level equivalent.
The adapter passes only signal and headers as per-call options, in chatStream(), structuredOutput() and structuredOutputStream(). So there is no way to opt 429 in.
Rate limits are the common transient failure on OpenRouter, especially when provider routing narrows a model to a single upstream provider. The OpenAI and Anthropic adapters retry 429 by default through their SDKs.
Steps to reproduce:
import { chat } from '@tanstack/ai'
import { createOpenRouterText } from '@tanstack/ai-openrouter'
import { HTTPClient } from '@openrouter/sdk/lib/http.js'
let calls = 0
const httpClient = new HTTPClient({
fetcher: async () => {
calls++
return new Response(
JSON.stringify({ error: { code: 429, message: 'Rate limit exceeded' } }),
{ status: 429, headers: { 'content-type': 'application/json', 'retry-after': '1' } },
)
},
})
const adapter = createOpenRouterText('openai/gpt-4o-mini', 'sk-or-test', {
httpClient,
retryConfig: {
strategy: 'backoff',
backoff: { initialInterval: 100, maxInterval: 1000, exponent: 1.5, maxElapsedTime: 3000 },
},
})
for await (const chunk of chat({ adapter, messages: [{ role: 'user', content: 'Hello' }] })) {
if (chunk.type === 'RUN_ERROR') console.log(chunk.message)
}
console.log(calls) // 1
Returning 503 instead of 429 from the same fetcher gives 5 calls.
Expected behavior
The adapter config accepts the SDK's retryCodes and forwards it to chat.send() next to signal and headers, e.g. retryCodes: ['429', '5XX']. Default behaviour stays as it is.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
Inline above. No API key needed, the fetcher is stubbed.
Do you intend to try to help solve this bug with your own PR?
Yes, PR follows.
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
- Dominant language
- TypeScript
- Stars
- 3.1k
- Forks
- 331
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 130
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from TanStack/ai
-
has-pr waiting-on: maintainer
-
waiting-on: maintainer
-
waiting-on: maintainer
-
has-pr waiting-on: maintainer
Difficulty 3/5 1-2 days Newbie friendliness 70/100
-
waiting-on: maintainer
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
mksglu/context-mode#1200 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
jaegertracing/jaeger-ui#4506 ·
-
area:desktop area:ui bug platform:macos
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
anthropics/claude-code#96687 ·
-
good first issue
Difficulty 1/5 Under an hour Newbie friendliness 95/100
AOSSIE-Org/DebateAI#582 · 2 comments ·