{"record":{"id":"f7e8a1d7d65df3cd","repo":"Hmbown/CodeWhale","slug":"release-response-exceeds-size-limit","errorCode":null,"errorMessage":"Release response exceeds size limit","messagePattern":"Release response exceeds size limit","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/lib/computer-use-release.ts","lineNumber":101,"sourceCode":"    url: `${COMPUTER_USE_REPO}/releases/tag/v${version}`,\n    downloadUrl: zip.browser_download_url as string,\n    receiptUrl: assets.receipt.browser_download_url as string,\n    verification: \"github-digest\",\n    ...(dmg ? { dmg } : {}),\n  };\n}\n\nasync function boundedJson(response: Response, limit: number): Promise<unknown> {\n  if (!response.body) throw new Error(\"Missing release response\");\n  const reader = response.body.getReader();\n  const decoder = new TextDecoder();\n  let length = 0, text = \"\";\n  try {\n    for (;;) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      length += value.byteLength;\n      if (length > limit) throw new Error(\"Release response exceeds size limit\");\n      text += decoder.decode(value, { stream: true });\n    }\n    return JSON.parse(text + decoder.decode());\n  } finally { await reader.cancel(); reader.releaseLock(); }\n}\n\nconst WEB_HEADERS = { \"User-Agent\": \"codewhale-web\" };\n\n/** GET a release web endpoint, following at most three 302s and only onto GitHub's release hosts over https. */\nasync function fetchReleaseWeb(url: string): Promise<Response> {\n  for (let hops = 0; ; hops++) {\n    const response = await fetch(url, { redirect: \"manual\", headers: WEB_HEADERS, signal: AbortSignal.timeout(5000) });\n    if (![301, 302, 307, 308].includes(response.status)) return response;\n    await response.body?.cancel();\n    const location = response.headers.get(\"location\");\n    if (!location) throw new Error(\"Release redirect without a location\");\n    const target = new URL(location, url);\n    if (hops >= 3 || target.protocol !== \"https:\" || !RELEASE_HOSTS.has(target.hostname)) {","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/lib/computer-use-release.ts#L83-L119","documentation":"boundedJson enforces a byte ceiling while streaming the release response; as soon as accumulated bytes exceed `limit`, it throws \"Release response exceeds size limit\" (cancelling the reader in finally). This bounds memory usage so a huge or malicious response cannot exhaust the worker.","triggerScenarios":"A release manifest or asset download larger than the configured limit (e.g. an unexpectedly large API response or an HTML error page served instead of a small JSON manifest).","commonSituations":"GitHub API returning an oversized response; limit tuned too low for a legitimate larger manifest; a proxy error page (multi-hundred-KB HTML) masquerading as the release endpoint; limit mismatch after moving to a richer release format.","solutions":["Raise the `limit` argument to accommodate the actual manifest size, if the growth is legitimate.","Verify the URL returns the small JSON manifest, not an HTML error page — check content-type and the beginning of the body.","Add a check on content-type/content-length before reading and reject non-JSON responses early.","Keep the limit as a guard: if the response genuinely should be small, investigate why it ballooned rather than disabling the check."],"exampleFix":"// before\nconst json = await boundedJson(res, 64 * 1024);\n\n// after (verify type, then use an adequate limit)\nconst ct = res.headers.get('content-type') ?? '';\nif (!ct.includes('application/json')) throw new Error('expected JSON manifest');\nconst json = await boundedJson(res, 256 * 1024);","handlingStrategy":"validation","validationCode":"const len = Number(res.headers.get('content-length') ?? '0');\nif (len > LIMIT) throw new Error(`release response too large: ${len} > ${LIMIT}`);\nif (!(res.headers.get('content-type') ?? '').includes('json')) throw new Error('release endpoint returned non-JSON');","typeGuard":null,"tryCatchPattern":"try {\n  return await boundedJson(res, LIMIT);\n} catch (e) {\n  if (e.message === 'Release response exceeds size limit') {\n    log.error('release payload too large — possible error page or oversized manifest', { url: res.url });\n    return cachedRelease();\n  }\n  throw e;\n}","preventionTips":["Check content-type and content-length before reading the body.","Size the limit to the largest legitimate manifest plus headroom.","Treat oversized responses as a signal the endpoint returned an error page, not as a reason to disable the cap.","Cache the last good release so failures degrade gracefully."],"tags":["network","json","size-limit"],"backgroundTag":"payload-too-large","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}