{"record":{"id":"fccacbfa437c0c74","repo":"Hmbown/CodeWhale","slug":"missing-release-response","errorCode":null,"errorMessage":"Missing release response","messagePattern":"Missing release response","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/lib/computer-use-release.ts","lineNumber":92,"sourceCode":"  if (receipt.version !== version || receipt.archive !== archive || receipt.platform !== \"macos\"\n    || receipt.arch !== \"universal\" || receipt.notarized !== true\n    || receipt.sha256 !== sha256 || receipt.size !== zip.size) return { status: \"pending\" };\n  // The image is offered only when the receipt and GitHub's digest agree on it; otherwise the archive alone is offered.\n  const image = receiptImage(receipt, version);\n  const dmg = assets.dmg && image && (assets.dmg.digest as string).slice(7) === image.sha256 && assets.dmg.size === image.size\n    ? { downloadUrl: assets.dmg.browser_download_url as string, size: image.size, sha256: image.sha256 } : undefined;\n  return {\n    status: \"ready\", version, sha256, size: zip.size as number,\n    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. */","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/lib/computer-use-release.ts#L74-L110","documentation":"boundedJson streams a Response body up to a size limit and JSON-parses it; it throws \"Missing release response\" immediately when response.body is null. This happens when the runtime hands back a body-less Response (e.g. certain redirects, opaque responses, or platform edge cases), so the release/receipt fetchers cannot read release data at all.","triggerScenarios":"A fetch of the release manifest/asset returns a Response whose body is null — typically from a network stack quirk, an already-consumed response, or a redirect the runtime did not follow with a body.","commonSituations":"Cloudflare Workers fetch returning a null body for certain upstream errors; passing a synthetic Response created without a body in tests; calling the fetcher twice and reusing a consumed response.","solutions":["Check response.ok and response.body before passing to boundedJson and fail with a clearer error naming the URL.","Create a fresh fetch for each call — never reuse a Response whose body may already be consumed.","If the upstream is flaky, retry the fetch once before giving up.","In tests, construct the Response with an explicit body: new Response(JSON.stringify(data), { status: 200 })."],"exampleFix":"// before\nconst json = await boundedJson(res, LIMIT);\n\n// after\nif (!res.ok || !res.body) throw new Error(`release fetch failed: ${res.status}`);\nconst json = await boundedJson(res, LIMIT);","handlingStrategy":"try-catch","validationCode":"if (!res.ok || !res.body) throw new Error(`release fetch failed for ${url}: status=${res.status}`);","typeGuard":"const hasBody = (res) => res instanceof Response && res.body !== null;","tryCatchPattern":"try {\n  const data = await boundedJson(res, LIMIT);\n} catch (e) {\n  if (e.message === 'Missing release response') {\n    // retry once with a fresh fetch, then surface a clear error\n    const fresh = await fetch(url, { redirect: 'follow' });\n    return getComputerUseRelease(...);\n  }\n  throw e;\n}","preventionTips":["Always fetch a fresh Response per call; never reuse consumed responses.","Follow redirects explicitly so the body-bearing response is returned.","In tests, construct Response objects with real bodies.","Check res.ok and body presence at the call site for clearer diagnostics."],"tags":["network","http","json"],"backgroundTag":"empty-response-body","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}