{"record":{"id":"08e04d64de106678","repo":"usebruno/bruno","slug":"failed-to-fetch-pac-err-response-status","errorCode":null,"errorMessage":"Failed to fetch PAC (${err.response.status})","messagePattern":"Failed to fetch PAC \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/bruno-requests/src/utils/pac-resolver.ts","lineNumber":62,"sourceCode":"    proxy: false,\n    responseType: 'text',\n    maxRedirects: 3\n  };\n\n  if (pacSource.startsWith('https://')) {\n    const agentOpts: AgentOptions = {\n      ca: tlsOptions.ca,\n      rejectUnauthorized: tlsOptions.rejectUnauthorized,\n      minVersion: tlsOptions.minVersion as AgentOptions['minVersion']\n    };\n    config.httpsAgent = new https.Agent(agentOpts);\n  }\n\n  try {\n    const response = await axios.get(pacSource, config);\n    return response.data;\n  } catch (err: any) {\n    if (err.response) throw new Error(`Failed to fetch PAC (${err.response.status})`);\n    throw err;\n  }\n}\n\nexport type GetPacResolverParams = {\n  pacSource: string;\n  httpsAgentRequestFields?: TlsOptions;\n  opts?: { cacheTtlMs?: number; timeoutMs?: number };\n};\n\nexport async function getPacResolver({ pacSource, httpsAgentRequestFields = {}, opts = {} }: GetPacResolverParams): Promise<PacWrapper> {\n  if (!pacSource) throw new Error('pacSource must be provided');\n\n  const cacheTtlMs = opts.cacheTtlMs ?? 5 * 60 * 1000;\n  let key: string;\n  if (pacSource.startsWith('https://')) {\n    const caRaw = httpsAgentRequestFields.ca;\n    const caHash = caRaw","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/usebruno/bruno/blob/9bdd81c7bdc57006e5f5ebffb79321a8d979f712/packages/bruno-requests/src/utils/pac-resolver.ts#L44-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the PAC URL by opening it in a browser or with curl -i; expect a 200 with JavaScript content.","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).","If 502/503: treat as transient and retry with backoff, or fall back to direct connection and warn the user.","If 404: correct the configured pac_url in the collection/system proxy settings."],"exampleFix":"// before\nconst pac = await getPacResolver({ pacSource: 'https://corp/proxy.pac' });\n\n// after\nlet pac;\ntry {\n  pac = await getPacResolver({ pacSource: 'https://corp/proxy.pac' });\n} catch (e) {\n  if (/Failed to fetch PAC \\(5\\d{2}\\)/.test(e.message)) {\n    // gateway hiccup — retry once, then degrade to direct\n    pac = await getPacResolver({ pacSource: 'https://corp/proxy.pac' }).catch(() => null);\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"async function pacReachable(url, { timeoutMs = 3000 } = {}) {\n  try {\n    const res = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(timeoutMs) });\n    if (!res.ok) return { ok: false, status: res.status };\n    const text = await res.text();\n    return { ok: /function\\s+FindProxyForURL/i.test(text), status: res.status, text };\n  } catch (e) { return { ok: false, status: 0, error: e }; }\n}\nconst probe = await pacReachable(pacSource);\nif (!probe.ok) throw new Error(`PAC not usable (${probe.status})`);","typeGuard":"function isPacFetchError(e) {\n  return e instanceof Error && /^Failed to fetch PAC \\(\\d+\\)$/.test(e.message);\n}\nfunction pacStatusFromError(e) {\n  const m = e?.message?.match(/Failed to fetch PAC \\((\\d+)\\)/);\n  return m ? Number(m[1]) : null;\n}","tryCatchPattern":"let pac;\ntry {\n  pac = await getPacResolver({ pacSource, httpsAgentRequestFields, opts });\n} catch (e) {\n  const status = pacStatusFromError(e);\n  if (status && status >= 500 && attemptsLeft > 0) {\n    await delay(500); return getPacResolverWithRetry(...); // retry 5xx once\n  }\n  if (status === 404 || status === 401 || status === 403) {\n    // non-transient — degrade to direct and warn\n    pac = null;\n  } else throw e;\n}","preventionTips":["Host the PAC on an unauthenticated, highly-available endpoint (the fetch disables upstream proxying).","Validate the PAC URL returns FindProxyForURL at config time, not just at first request.","Cache the resolved PAC aggressively (the library caches for 5 min) to absorb short outages.","Distinguish 5xx (retry/fallback) from 4xx (correct the URL)."],"tags":["network","proxy","pac","configuration","http"],"backgroundTag":null,"analyzedSha":"9bdd81c7bdc57006e5f5ebffb79321a8d979f712","analyzedAt":"2026-08-13T04:09:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}