Skip to content

RetryHandler: retryOptions.retryAfter: false is ignored — retry delay still follows the Retry-After header #5682

Description

@codeAnqiang-ma

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions