{"record":{"id":"9cd373a710e99d16","repo":"vercel/next.js","slug":"could-not-fetch-upgrade-metadata-please-try-again","errorCode":null,"errorMessage":"Could not fetch upgrade metadata. Please try again.","messagePattern":"Could not fetch upgrade metadata\\. Please try again\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/next/src/lib/upgrade/prepare-upgrade.ts","lineNumber":205,"sourceCode":"async function fetchJSON(\n  url: string,\n  init: RequestInit | undefined = undefined\n): Promise<{ value: unknown; headers: Headers }> {\n  try {\n    const response = await fetch(url, {\n      ...init,\n      headers: { Accept: 'application/json', ...init?.headers },\n      signal: AbortSignal.timeout(10_000),\n      redirect: 'error',\n    })\n\n    if (!response.ok) {\n      throw new Error(`HTTP ${response.status}`)\n    }\n\n    return { value: await response.json(), headers: response.headers }\n  } catch (error) {\n    throw new Error('Could not fetch upgrade metadata. Please try again.', {\n      cause: error,\n    })\n  }\n}\n\nfunction parseReleases(value: unknown): SecuritySnapshot['releases'] {\n  const data = value as {\n    versions:\n      | Record<\n          string,\n          {\n            version: string\n          }\n        >\n      | undefined\n  }\n\n  if (!data?.versions) {","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/vercel/next.js/blob/34433fd12ee8074ea3f47af9f36255c7390d0301/packages/next/src/lib/upgrade/prepare-upgrade.ts#L187-L223","documentation":"fetchJSON wraps every failure of an HTTP fetch (network errors, timeouts from the 10s AbortSignal, redirects, non-2xx statuses, invalid JSON bodies) into a single generic error with the original error attached as `cause`. The message is intentionally generic because the underlying cause (in error.cause) carries the detail.","triggerScenarios":"Any fetchJSON call in prepare-upgrade.ts (npm registry next/latest, registry document, GitHub advisories API, npm bulk advisory endpoint) when: the network is down, the 10s timeout fires, the server returns a non-ok status (rethrown as `HTTP <status>` then wrapped), a redirect occurs (redirect: 'error'), or the response body is not valid JSON.","commonSituations":"Offline or firewalled CI environments; GitHub API rate limiting (HTTP 403/429); corporate proxies blocking api.github.com; npm registry outages; responses blocked by a proxy returning HTML instead of JSON.","solutions":["Read error.cause to find the real failure (HTTP status, timeout, DNS error) and address it.","Retry the command — the message itself says 'Please try again' and failures may be transient rate limits.","Check network/proxy access to registry.npmjs.org and api.github.com from the environment.","If rate limited by GitHub, wait for the rate-limit window to reset or use authenticated access.","Set NEXT_TELEMETRY_DISABLED and verify no proxy env vars (HTTP_PROXY/HTTPS_PROXY) interfere."],"exampleFix":"// before\nconst { value } = await fetchJSON(url) // generic wrapper error\n// after\ntry {\n  const { value } = await fetchJSON(url)\n} catch (error) {\n  console.error('cause:', (error as Error).cause) // e.g. Error: HTTP 403\n}","handlingStrategy":"retry","validationCode":"// preflight connectivity before calling upgrade APIs\nasync function canReach(url: string) {\n  try {\n    const res = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) })\n    return res.ok || res.status < 500\n  } catch { return false }\n}\nawait Promise.all([canReach('https://registry.npmjs.org/next/latest'), canReach('https://api.github.com/advisories?per_page=1')])","typeGuard":"function hasCause(error: unknown): error is Error & { cause: unknown } {\n  return error instanceof Error && 'cause' in error\n}","tryCatchPattern":"try {\n  const prep = await prepareUpgrade(dir, 'latest')\n} catch (error) {\n  const cause = (error as Error).cause\n  if (String(cause).includes('HTTP 403') || String(cause).includes('HTTP 429')) {\n    // rate limited — back off and retry later\n  } else if (String(cause).includes('TimeoutError')) {\n    // network slow — retry with better connectivity\n  }\n  throw error\n}","preventionTips":["Run upgrades on a machine with verified access to registry.npmjs.org and api.github.com","Always inspect error.cause — the top-level message is intentionally generic","Add retry with backoff around prepareUpgrade in CI","Watch for GitHub API rate limits (unauthenticated: 60 req/h per IP)"],"tags":["network","http","fetch","timeout"],"backgroundTag":"http-request-failed","analyzedSha":"34433fd12ee8074ea3f47af9f36255c7390d0301","analyzedAt":"2026-09-20T18:20:20.576Z","contentChangedAt":"2026-09-20T18:20:20.576Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}