{"record":{"id":"989b18ba357e978c","repo":"santifer/career-ops","slug":"too-many-redirects-max-redirects-for-url","errorCode":null,"errorMessage":"too many redirects (>${MAX_REDIRECTS}) for ${url}","messagePattern":"too many redirects \\(>(.+?)\\) for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"plugins/_engine.mjs","lineNumber":419,"sourceCode":"        await resolveAndValidate(next.hostname, { allowsLocalhost });\n        if (next.hostname !== current.hostname) {\n          // Don't forward credentials across a hostname change (what the\n          // platform fetch does for cross-origin redirects; we do it manually).\n          reqHeaders = Object.fromEntries(Object.entries(reqHeaders).filter(([k]) => !/^(authorization|cookie)$/i.test(k)));\n        }\n        current = next;\n        continue;\n      }\n      if (!res.ok) {\n        const snippet = (await res.text().catch(() => '')).replace(/\\s+/g, ' ').trim().slice(0, 300);\n        const err = new Error(snippet ? `HTTP ${res.status}: ${snippet}` : `HTTP ${res.status}`);\n        // @ts-ignore\n        err.status = res.status;\n        throw err;\n      }\n      return res;\n    }\n    throw new Error(`too many redirects (>${MAX_REDIRECTS}) for ${url}`);\n  };\n}\n\n/**\n * Build the least-privilege ctx for a plugin. The scoped frozen env is a\n * CONVENIENCE (process.env is still globally reachable from any module) — the\n * real boundary is code review + trust.\n * @param {PluginManifestNormalized} manifest\n * @param {{ dryRun?: boolean, settings?: object }} [opts]\n * @returns {PluginContext}\n */\nexport function buildCtx(manifest, opts = {}) {\n  const scoped = {};\n  for (const name of [...manifest.requiredEnv, ...manifest.optionalEnv]) {\n    if (process.env[name] !== undefined) scoped[name] = process.env[name];\n  }\n  const env = Object.freeze({ ...scoped });\n  // Secret values long enough to be worth redacting (avoid no-op/over-redaction","sourceCodeStart":401,"sourceCodeEnd":437,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/plugins/_engine.mjs#L401-L437","documentation":"Thrown by guardedFetch() when the redirect chain exceeds MAX_REDIRECTS (5) hops without reaching a final non-3xx response. The guard follows redirects manually (redirect:'manual'), re-validating scheme + host + resolved IP at each hop, and caps the chain to prevent redirect loops/bombs.","triggerScenarios":"A URL redirects more than 5 times (loop or deep chain); two URLs redirect to each other (A→B→A); a misconfigured endpoint redirects in a circle; an intentionally redirect-heavy adversarial endpoint.","commonSituations":"ATS endpoint with a stale session that keeps redirecting to a login page; misconfigured CDN redirect loop (http↔https or www↔apex); a plugin target moved and the redirect chain is unusually deep.","solutions":["Fetch the URL with curl -IL to inspect the redirect chain and find the loop/deep link.","Fix or replace the source URL with the final canonical destination.","If the chain is legitimate but long, request a config change to shorten it server-side.","Do not raise MAX_REDIRECTS casually — a loop will still loop; root-cause the chain."],"exampleFix":"// before: url redirects A->B->A->... (loop)\nawait ctx.fetch('https://api.example.com/old');\n// after: call the final canonical URL directly\nawait ctx.fetch('https://api.example.com/v2/resource');","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  const res = await ctx.fetch(url);\n} catch (e) {\n  if (/too many redirects/.test(e.message)) {\n    // resolve the final URL via curl -IL, replace `url`, retry once\n  } else throw e;\n}","preventionTips":["Prefer the final canonical URL over a redirecting alias.","Investigate chains with curl -IL before wiring a URL into a plugin.","Don't raise MAX_REDIRECTS to mask a loop — root-cause it."],"tags":["plugins","network","redirects","egress","loop"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}