{"record":{"id":"1a700ab3b005f49a","repo":"nodejs/node","slug":"redirect-loop-detected-cannot-redirect-to-origi","errorCode":null,"errorMessage":"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.","messagePattern":"Redirect loop detected\\. Cannot redirect to (.+?)\\. This typically happens when using a Client or Pool with cross-origin redirects\\. Use an Agent for cross-origin redirects\\.","errorType":"exception","errorClass":"InvalidArgumentError","httpStatus":null,"severity":"error","filePath":"deps/undici/src/lib/handler/redirect-handler.js","lineNumber":102,"sourceCode":"    if (this.opts.origin) {\n      this.history.push(new URL(this.opts.path, this.opts.origin))\n    }\n\n    if (!this.location) {\n      this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)\n      return\n    }\n\n    const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)))\n    const path = search ? `${pathname}${search}` : pathname\n\n    // Check for redirect loops by seeing if we've already visited this URL in our history\n    // This catches the case where Client/Pool try to handle cross-origin redirects but fail\n    // and keep redirecting to the same URL in an infinite loop\n    const redirectUrlString = `${origin}${path}`\n    for (const historyUrl of this.history) {\n      if (historyUrl.toString() === redirectUrlString) {\n        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.`)\n      }\n    }\n\n    // Remove headers referring to the original URL.\n    // By default it is Host only. A 303 or a 301/302 POST-to-GET redirect also removes all Content-* headers.\n    // https://tools.ietf.org/html/rfc7231#section-6.4\n    this.opts.headers = cleanRequestHeaders(this.opts.headers, removeContentHeaders, this.opts.origin !== origin, this.stripHeadersOnRedirect, this.stripHeadersOnCrossOriginRedirect)\n    this.opts.path = path\n    this.opts.origin = origin\n    this.opts.query = null\n  }\n\n  onResponseData (controller, chunk) {\n    if (this.location) {\n      /*\n        https://tools.ietf.org/html/rfc7231#section-6.4\n\n        TLDR: undici always ignores 3xx response bodies.","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/deps/undici/src/lib/handler/redirect-handler.js#L84-L120","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst client = new Client('https://api.example.com')\nawait client.request({ path: '/me', maxRedirections: 5 }) // 3xx -> https://auth.example.com\n\n// after\nconst agent = new Agent() // can follow cross-origin redirects\nawait agent.request({ origin: 'https://api.example.com', path: '/me', maxRedirections: 5 })","handlingStrategy":"validation","validationCode":"function pickDispatcher(targetOrigin, agent, client) {\n  // Client/Pool are single-origin; cross-origin redirects need an Agent\n  return client.origin === targetOrigin ? client : agent\n}","typeGuard":null,"tryCatchPattern":"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 }","preventionTips":["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."],"tags":["undici","http","redirect","cross-origin","client-pool","agent"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}