usebruno/bruno · error · Error

Failed to fetch PAC ( )

Error message

Failed to fetch PAC (${err.response.status})

What it means

Thrown by downloadPac when axios.get(pacSource) completes with an HTTP response (err.response is defined) whose status is not 2xx. It distinguishes an HTTP-level failure of the PAC host from a transport-level failure (DNS, timeout, connection refused) which is re-thrown unchanged. The status code is embedded so the caller can decide whether to retry, fall back, or surface to the user.

Solutions

  1. Verify the PAC URL by opening it in a browser or with curl -i; expect a 200 with JavaScript content.
  2. If 401/403: provide the required auth (the library disables axios proxy with proxy:false, so authenticating to an upstream proxy is not supported — host the PAC on an unauthenticated endpoint instead).
  3. If 502/503: treat as transient and retry with backoff, or fall back to direct connection and warn the user.
  4. If 404: correct the configured pac_url in the collection/system proxy settings.

Example fix

// before
const pac = await getPacResolver({ pacSource: 'https://corp/proxy.pac' });

// after
let pac;
try {
  pac = await getPacResolver({ pacSource: 'https://corp/proxy.pac' });
} catch (e) {
  if (/Failed to fetch PAC \(5\d{2}\)/.test(e.message)) {
    // gateway hiccup — retry once, then degrade to direct
    pac = await getPacResolver({ pacSource: 'https://corp/proxy.pac' }).catch(() => null);
  } else throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

async function pacReachable(url, { timeoutMs = 3000 } = {}) {
  try {
    const res = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(timeoutMs) });
    if (!res.ok) return { ok: false, status: res.status };
    const text = await res.text();
    return { ok: /function\s+FindProxyForURL/i.test(text), status: res.status, text };
  } catch (e) { return { ok: false, status: 0, error: e }; }
}
const probe = await pacReachable(pacSource);
if (!probe.ok) throw new Error(`PAC not usable (${probe.status})`);

Type guard

function isPacFetchError(e) {
  return e instanceof Error && /^Failed to fetch PAC \(\d+\)$/.test(e.message);
}
function pacStatusFromError(e) {
  const m = e?.message?.match(/Failed to fetch PAC \((\d+)\)/);
  return m ? Number(m[1]) : null;
}

Try / catch

let pac;
try {
  pac = await getPacResolver({ pacSource, httpsAgentRequestFields, opts });
} catch (e) {
  const status = pacStatusFromError(e);
  if (status && status >= 500 && attemptsLeft > 0) {
    await delay(500); return getPacResolverWithRetry(...); // retry 5xx once
  }
  if (status === 404 || status === 401 || status === 403) {
    // non-transient — degrade to direct and warn
    pac = null;
  } else throw e;
}

Prevention

When it happens

Trigger: getPacResolver/downloadPac is called with a pacSource pointing to a PAC URL that returns 401/403 (auth required), 404 (wrong URL), 500/502/503 (proxy host down or misconfigured gateway), or any other non-2xx. The catch only fires when err.response exists; pure network errors propagate as the original axios error.

Common situations: Corporate PAC URL changed and the configured value is now stale (404); PAC host sits behind SSO/auth that rejects unauthenticated requests (401/403); the PAC-serving reverse proxy is temporarily down (502/503); typo in the URL; PAC endpoint moved off the default port.

Related errors


AI-assisted analysis of usebruno/bruno@9bdd81c7bd (2026-08-13). Data as JSON: /api/errors/08e04d64de106678. Report an issue: GitHub.

Appendix: source

Thrown at packages/bruno-requests/src/utils/pac-resolver.ts:62

    proxy: false,
    responseType: 'text',
    maxRedirects: 3
  };

  if (pacSource.startsWith('https://')) {
    const agentOpts: AgentOptions = {
      ca: tlsOptions.ca,
      rejectUnauthorized: tlsOptions.rejectUnauthorized,
      minVersion: tlsOptions.minVersion as AgentOptions['minVersion']
    };
    config.httpsAgent = new https.Agent(agentOpts);
  }

  try {
    const response = await axios.get(pacSource, config);
    return response.data;
  } catch (err: any) {
    if (err.response) throw new Error(`Failed to fetch PAC (${err.response.status})`);
    throw err;
  }
}

export type GetPacResolverParams = {
  pacSource: string;
  httpsAgentRequestFields?: TlsOptions;
  opts?: { cacheTtlMs?: number; timeoutMs?: number };
};

export async function getPacResolver({ pacSource, httpsAgentRequestFields = {}, opts = {} }: GetPacResolverParams): Promise<PacWrapper> {
  if (!pacSource) throw new Error('pacSource must be provided');

  const cacheTtlMs = opts.cacheTtlMs ?? 5 * 60 * 1000;
  let key: string;
  if (pacSource.startsWith('https://')) {
    const caRaw = httpsAgentRequestFields.ca;
    const caHash = caRaw

View on GitHub (pinned to 9bdd81c7bd)