santifer/career-ops · warning · Error
too many redirects (>${MAX_REDIRECTS}) for ${url}
Error message
too many redirects (>${MAX_REDIRECTS}) for ${url} What it means
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.
Source
Thrown at plugins/_engine.mjs:419
await resolveAndValidate(next.hostname, { allowsLocalhost });
if (next.hostname !== current.hostname) {
// Don't forward credentials across a hostname change (what the
// platform fetch does for cross-origin redirects; we do it manually).
reqHeaders = Object.fromEntries(Object.entries(reqHeaders).filter(([k]) => !/^(authorization|cookie)$/i.test(k)));
}
current = next;
continue;
}
if (!res.ok) {
const snippet = (await res.text().catch(() => '')).replace(/\s+/g, ' ').trim().slice(0, 300);
const err = new Error(snippet ? `HTTP ${res.status}: ${snippet}` : `HTTP ${res.status}`);
// @ts-ignore
err.status = res.status;
throw err;
}
return res;
}
throw new Error(`too many redirects (>${MAX_REDIRECTS}) for ${url}`);
};
}
/**
* Build the least-privilege ctx for a plugin. The scoped frozen env is a
* CONVENIENCE (process.env is still globally reachable from any module) — the
* real boundary is code review + trust.
* @param {PluginManifestNormalized} manifest
* @param {{ dryRun?: boolean, settings?: object }} [opts]
* @returns {PluginContext}
*/
export function buildCtx(manifest, opts = {}) {
const scoped = {};
for (const name of [...manifest.requiredEnv, ...manifest.optionalEnv]) {
if (process.env[name] !== undefined) scoped[name] = process.env[name];
}
const env = Object.freeze({ ...scoped });
// Secret values long enough to be worth redacting (avoid no-op/over-redactionView on GitHub (pinned to 9b17a8ac97)
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.
Example fix
// before: url redirects A->B->A->... (loop)
await ctx.fetch('https://api.example.com/old');
// after: call the final canonical URL directly
await ctx.fetch('https://api.example.com/v2/resource'); Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
try {
const res = await ctx.fetch(url);
} catch (e) {
if (/too many redirects/.test(e.message)) {
// resolve the final URL via curl -IL, replace `url`, retry once
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- clone of ${url}@${sha.slice(0, 10)} failed — ${err.stderr ?
- plugin egress must use HTTPS: ${u.href}
- plugin egress to "${u.hostname}" is not in allowedHosts [${[
- plugin egress to ${hostname} is blocked (private/loopback/me
- plugin egress: cannot resolve ${hostname} — ${err.message}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/989b18ba357e978c.
Report an issue: GitHub.