{"record":{"id":"78b3ac30bfa70e3c","repo":"shadcn-ui/ui","slug":"too-many-redirects-while-fetching-url","errorCode":null,"errorMessage":"Too many redirects while fetching ${url}","messagePattern":"Too many redirects while fetching (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shadcn/src/registry/proxy.ts","lineNumber":131,"sourceCode":"\n    if (nextUrl.origin !== originalOrigin) {\n      // Cross-origin hop: drop every caller-supplied header except the\n      // non-sensitive Accept/User-Agent.\n      const stripped = new Headers()\n      originalHeaders.forEach((value, key) => {\n        if (SAFE_HEADER_NAMES.has(key.toLowerCase())) {\n          stripped.set(key, value)\n        }\n      })\n      headers = stripped\n    } else {\n      headers = originalHeaders\n    }\n\n    currentUrl = nextUrl.toString()\n  }\n\n  throw new Error(`Too many redirects while fetching ${url}`)\n}\n\nasync function fetchOnce(\n  url: string,\n  init: RequestInit | undefined,\n  headers: Headers\n) {\n  try {\n    // The `dispatcher` option is supported by Node's fetch at runtime but\n    // missing from the ambient RequestInit type, hence the cast. Redirects are\n    // followed manually (see fetchWithProxy) so headers can be re-scoped per\n    // hop, hence `redirect: \"manual\"`.\n    //\n    // This must stay the global `fetch` binding: MSW's Node adapter patches\n    // `globalThis.fetch`, so importing `fetch` from undici here would bypass\n    // MSW's interceptor and break the registry tests.\n    return await fetch(url, {\n      ...init,","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/shadcn-ui/ui/blob/efac5987074af84ece57c367c6dd83387b967022/packages/shadcn/src/registry/proxy.ts#L113-L149","documentation":"Thrown by fetchWithProxy when following HTTP redirects exceeds MAX_REDIRECTS (5 hops). Redirects are followed manually so that cross-origin hops can strip sensitive caller headers. After 5+1 iterations of the loop without a non-redirect response, the loop exits and throws a plain Error (not a RegistryError - it has no code). This typically indicates a redirect loop or a server with an unusually long redirect chain.","triggerScenarios":"fetchWithProxy(url) where the server responds with 3xx + Location more than 5 times in a row. Each iteration requires status 300-399 and a 'location' header to count as a redirect. Common with redirect loops (A -> B -> A) or auth-gated CDNs that bounce through multiple login redirects.","commonSituations":"A misconfigured registry URL that redirects to itself or to a chain longer than 5. A private registry behind SSO that redirects through identity providers. HTTP -> HTTPS -> www -> auth cascades. A dying proxy inserting extra redirects.","solutions":["Curl the URL with -IL to see the redirect chain and identify the loop or long chain: curl -sIL -o /dev/null -w '%{redirect_url}\\n' <url>.","Use the final (canonical) URL directly so no redirect following is needed.","Fix the server-side redirect loop if you control it.","If a proxy is injecting redirects, unset HTTP_PROXY/HTTPS_PROXY/ALL_PROXY and retry."],"exampleFix":"// before - URL redirects through 6+ hops\nawait fetchWithProxy(\"https://example.com/registry/button.json\")\n\n// after - resolve the canonical URL via curl -IL, then use it\nawait fetchWithProxy(\"https://cdn.example.com/v2/button.json\")","handlingStrategy":"try-catch","validationCode":"async function assertShortRedirectChain(url: string, max = 5) {\n  let current = url\n  for (let i = 0; i <= max; i++) {\n    const res = await fetch(current, { redirect: \"manual\" })\n    if (res.status < 300 || res.status >= 400 || !res.headers.get(\"location\")) return\n    current = new URL(res.headers.get(\"location\")!, current).toString()\n  }\n  throw new Error(`${url} exceeds ${max} redirects`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fetchWithProxy(url)\n} catch (err) {\n  if (err instanceof Error && /Too many redirects/.test(err.message)) {\n    // resolve the canonical URL via curl -IL and pass that instead\n  }\n  throw err\n}","preventionTips":["Use canonical (final) registry URLs that do not redirect.","Unset HTTP_PROXY/HTTPS_PROXY/ALL_PROXY if a proxy injects redirects.","Curl -IL the URL to inspect the chain before wiring it into config."],"tags":["network","redirect","fetch","proxy","http"],"backgroundTag":null,"analyzedSha":"efac5987074af84ece57c367c6dd83387b967022","analyzedAt":"2026-08-12T05:00:50.218Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}