Bug Description
retryOptions.retryAfter is documented as a boolean that controls whether the retry delay is inferred from the Retry-After response header:
retryAfter {boolean} When true, the delay before the next retry is inferred from the Retry-After response header when present. Default: true.
(docs/docs/api/RetryHandler.md, same wording in RetryAgent.md; the JSDoc in types/retry-handler.d.ts also describes it as a toggle with @default true.)
However, the option is never read. It is normalized and stored in the constructor (lib/handler/retry-handler.js#L84), but the default retry strategy uses the header unconditionally (lib/handler/retry-handler.js#L225):
let retryAfterHeader = headers?.['retry-after']
So setting retryAfter: false has no effect: the server keeps controlling the retry delay (up to maxTimeout, default 30s per retry) instead of the configured exponential backoff. This affects RetryHandler, RetryAgent and interceptors.retry() alike.
Reproducible By
'use strict'
const { createServer } = require('node:http')
const { Agent, request, interceptors } = require('undici')
const server = createServer((req, res) => {
res.writeHead(429, { 'retry-after': '10' })
res.end('rate limited')
}).listen(0, async () => {
const origin = `http://localhost:${server.address().port}`
const dispatcher = new Agent().compose(interceptors.retry({
retryAfter: false, // per docs: disable Retry-After based delay inference
maxRetries: 1,
minTimeout: 100,
statusCodes: [429]
}))
const start = Date.now()
try {
await request(`${origin}/`, { dispatcher })
} catch (err) {
console.log(`failed with ${err.name} after ${Date.now() - start}ms`)
}
server.close()
await dispatcher.close()
})
Output on main (2afeee4):
failed with RequestRetryError after 10037ms
Expected Behavior
With retryAfter: false the single retry should be scheduled by the exponential backoff (minTimeout * timeoutFactor ** (counter - 1) = 100ms), so the request should fail after roughly 100-400ms, not after the server-dictated 10s.
Environment
macOS, Node.js v22.22.3, undici main @ 2afeee4
Additional context
Found during a code correctness review of the retry handler. I have a minimal fix with a regression test ready and will open a PR. This report was prepared with AI assistance; I reproduced the behavior locally and reviewed every conclusion.
Bug Description
retryOptions.retryAfteris documented as a boolean that controls whether the retry delay is inferred from theRetry-Afterresponse header:(docs/docs/api/RetryHandler.md, same wording in RetryAgent.md; the JSDoc in
types/retry-handler.d.tsalso describes it as a toggle with@default true.)However, the option is never read. It is normalized and stored in the constructor (
lib/handler/retry-handler.js#L84), but the default retry strategy uses the header unconditionally (lib/handler/retry-handler.js#L225):So setting
retryAfter: falsehas no effect: the server keeps controlling the retry delay (up tomaxTimeout, default 30s per retry) instead of the configured exponential backoff. This affectsRetryHandler,RetryAgentandinterceptors.retry()alike.Reproducible By
Output on
main(2afeee4):Expected Behavior
With
retryAfter: falsethe single retry should be scheduled by the exponential backoff (minTimeout * timeoutFactor ** (counter - 1)= 100ms), so the request should fail after roughly 100-400ms, not after the server-dictated 10s.Environment
macOS, Node.js v22.22.3, undici
main@ 2afeee4Additional context
Found during a code correctness review of the retry handler. I have a minimal fix with a regression test ready and will open a PR. This report was prepared with AI assistance; I reproduced the behavior locally and reviewed every conclusion.