nodejs/node · error · InvalidArgumentError
Redirect loop detected. Cannot redirect to ${origin}. This t
Error message
Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects. What it means
Thrown as an InvalidArgumentError from RedirectHandler.onResponseStart when a URL about to be followed already exists in this.history, i.e. the handler is about to revisit a URL it has already been to. Undici's Client and Pool are bound to a single origin and cannot themselves perform cross-origin redirects, so a cross-origin redirect bounces back to the same URL forever; this guard turns that infinite loop into a single explicit error and points you at the fix (use an Agent).
Source
Thrown at deps/undici/src/lib/handler/redirect-handler.js:102
if (this.opts.origin) {
this.history.push(new URL(this.opts.path, this.opts.origin))
}
if (!this.location) {
this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
return
}
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)))
const path = search ? `${pathname}${search}` : pathname
// Check for redirect loops by seeing if we've already visited this URL in our history
// This catches the case where Client/Pool try to handle cross-origin redirects but fail
// and keep redirecting to the same URL in an infinite loop
const redirectUrlString = `${origin}${path}`
for (const historyUrl of this.history) {
if (historyUrl.toString() === redirectUrlString) {
throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`)
}
}
// Remove headers referring to the original URL.
// By default it is Host only. A 303 or a 301/302 POST-to-GET redirect also removes all Content-* headers.
// https://tools.ietf.org/html/rfc7231#section-6.4
this.opts.headers = cleanRequestHeaders(this.opts.headers, removeContentHeaders, this.opts.origin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect)
this.opts.path = path
this.opts.origin = origin
this.opts.query = null
}
onResponseData (controller, chunk) {
if (this.location) {
/*
https://tools.ietf.org/html/rfc7231#section-6.4
TLDR: undici always ignores 3xx response bodies.View on GitHub (pinned to 1b2de5e052)
Solutions
- Use an Agent instead of a Client/Pool; Agent can resolve and dispatch to the new origin on each redirect.
- If you must use a Client/Pool, disable redirect following (maxRedirections: 0) and resolve cross-origin Locations manually.
- Check the redirect target's origin; if it differs from the client origin, issue a fresh request to a properly scoped dispatcher.
Example fix
// before
const client = new Client('https://api.example.com')
await client.request({ path: '/me', maxRedirections: 5 }) // 3xx -> https://auth.example.com
// after
const agent = new Agent() // can follow cross-origin redirects
await agent.request({ origin: 'https://api.example.com', path: '/me', maxRedirections: 5 }) Defensive patterns
Strategy: validation
Validate before calling
function pickDispatcher(targetOrigin, agent, client) {
// Client/Pool are single-origin; cross-origin redirects need an Agent
return client.origin === targetOrigin ? client : agent
} Try / catch
try { await client.request({ path, maxRedirections: 5 }) } catch (e) { if (e.code === 'UND_ERR_INVALID_ARG' && /Redirect loop detected/.test(e.message)) { await agent.request({ origin: targetOrigin, path, maxRedirections: 5 }) } else throw e } Prevention
- Use an Agent whenever the target may redirect across origins.
- Keep Client/Pool for single-origin calls with maxRedirections: 0.
- Resolve the final origin before dispatch when you know it up front.
When it happens
Trigger: Using a Client or Pool (single-origin) against a server that issues a cross-origin 3xx redirect that the server then redirects back from, creating an A->B->A cycle. The handler detects the repeat URL and aborts.
Common situations: Pointing a Pool/Client at a load balancer or CDN that redirects to a different host; SSO/OAuth flows that hop domains; testing against httpbin-like services that echo redirects across origins.
Related errors
- maxRedirections must be a positive number
- throwOnMaxRedirect must be a boolean
- max redirects
- ${optionName} must be an array
- ${optionName} must contain header names
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/1a700ab3b005f49a.
Report an issue: GitHub.