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-redaction

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Fetch the URL with curl -IL to inspect the redirect chain and find the loop/deep link.
  2. Fix or replace the source URL with the final canonical destination.
  3. If the chain is legitimate but long, request a config change to shorten it server-side.
  4. 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

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


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/989b18ba357e978c. Report an issue: GitHub.