{"record":{"id":"30b7ca46d8c5727f","repo":"paperclipai/paperclip","slug":"invalid-response-photon-returned-an-empty","errorCode":"invalid_response","errorMessage":"Photon returned an empty response","messagePattern":"Photon returned an empty response","errorType":"error_code","errorClass":"PhotonError","httpStatus":null,"severity":"error","filePath":"server/src/services/photon/cloud.ts","lineNumber":158,"sourceCode":"    }\n    if (response.status === 401 || response.status === 403)\n      throw new PhotonError(\n        \"credentials\",\n        \"Photon rejected this project ID or secret\",\n      );\n    if (response.status === 429)\n      throw new PhotonError(\n        \"quota\",\n        \"Photon Cloud request limit reached; retry later\",\n      );\n    if (!response.ok)\n      throw new PhotonError(\n        \"network\",\n        `Photon Cloud returned HTTP ${response.status}`,\n      );\n    const reader = response.body?.getReader();\n    if (!reader)\n      throw new PhotonError(\n        \"invalid_response\",\n        \"Photon returned an empty response\",\n      );\n    const chunks: Uint8Array[] = [];\n    let length = 0;\n    try {\n      for (;;) {\n        const next = await reader.read();\n        if (next.done) break;\n        length += next.value.length;\n        if (length > MAX_RESPONSE_BYTES)\n          throw new PhotonError(\n            \"invalid_response\",\n            \"Photon project response exceeds the supported size\",\n          );\n        chunks.push(next.value);\n      }\n      const body: unknown = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/cloud.ts#L140-L176","documentation":"Thrown by PhotonCloudClient.request when the successful HTTP response has no readable body — response.body is undefined/null so response.body.getReader() cannot be called. The client requires a streaming JSON body and classifies a missing one as PhotonError 'invalid_response'.","triggerScenarios":"The Cloud endpoint returns 2xx with an empty or null body (e.g. HEAD-like semantics, 204, or an infrastructure layer stripping the body); a custom fetchImpl passed to the PhotonCloudClient constructor that does not populate response.body; polyfill/undici environments without streaming body support.","commonSituations":"Tests or code injecting a mock fetch whose Response lacks a body; middleware/proxies returning 200 with zero-length bodies; environments (old runtimes) where fetch responses have no ReadableStream body.","solutions":["Use the default global fetch (or a mock that returns a real Response with a string/stream body)","Verify any custom fetchImpl passed to new PhotonCloudClient(fetchImpl) returns responses with a non-null body","Check intermediary proxies/load balancers that might strip response bodies","Inspect the exact request; a 204-style response to what should be a data endpoint indicates a Photon API change — confirm the endpoint path is current"],"exampleFix":"// before: mock without body\nconst cloud = new PhotonCloudClient(async () => new Response(null, { status: 200 }));\n// after: mock with body\nconst cloud = new PhotonCloudClient(async () =>\n  new Response(JSON.stringify({ succeed: true, data: {} }), { status: 200 }));","handlingStrategy":"type-guard","validationCode":"// preflight the custom fetch to prove it yields bodied responses\nconst probe = await fetchImpl(\"https://example.com\");\nif (!probe.body) throw new Error(\"fetchImpl must return responses with a body stream\");","typeGuard":"function hasBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {\n  return res.body != null;\n}","tryCatchPattern":"try {\n  return await cloud.inspect(projectId, secret);\n} catch (e) {\n  if (e instanceof PhotonError && e.code === \"invalid_response\" && e.message.includes(\"empty response\"))\n    log.error(\"Photon fetchImpl returned a bodiless 2xx — check custom fetch or proxy\");\n  throw e;\n}","preventionTips":["Use the default global fetch unless you have a specific need for a custom fetchImpl","In tests, build mocks with new Response(JSON.stringify(...)) so body is populated","Check proxies/CDN layers for body-stripping behavior on 2xx responses","Verify runtime support for streaming response bodies (undici/Node 18+)","Treat empty 2xx bodies as an infrastructure bug, not a Photon contract"],"tags":["empty-body","invalid-response","fetch","stream","photon-cloud"],"backgroundTag":"empty-api-response","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}