paperclipai/paperclip · error · PhotonError
invalid_response
invalid_response
Error message
Photon returned an empty response
What it means
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'.
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
Example fix
// before: mock without body
const cloud = new PhotonCloudClient(async () => new Response(null, { status: 200 }));
// after: mock with body
const cloud = new PhotonCloudClient(async () =>
new Response(JSON.stringify({ succeed: true, data: {} }), { status: 200 })); Defensive patterns
Strategy: type-guard
Validate before calling
// preflight the custom fetch to prove it yields bodied responses
const probe = await fetchImpl("https://example.com");
if (!probe.body) throw new Error("fetchImpl must return responses with a body stream"); Type guard
function hasBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {
return res.body != null;
} Try / catch
try {
return await cloud.inspect(projectId, secret);
} catch (e) {
if (e instanceof PhotonError && e.code === "invalid_response" && e.message.includes("empty response"))
log.error("Photon fetchImpl returned a bodiless 2xx — check custom fetch or proxy");
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.
Related errors
- network
- Announcement request failed
- CreateOS connection failed.
- CreateOS process stream ended without an exit status.
- CreateOS process stream reported an error.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/30b7ca46d8c5727f.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/cloud.ts:158
}
if (response.status === 401 || response.status === 403)
throw new PhotonError(
"credentials",
"Photon rejected this project ID or secret",
);
if (response.status === 429)
throw new PhotonError(
"quota",
"Photon Cloud request limit reached; retry later",
);
if (!response.ok)
throw new PhotonError(
"network",
`Photon Cloud returned HTTP ${response.status}`,
);
const reader = response.body?.getReader();
if (!reader)
throw new PhotonError(
"invalid_response",
"Photon returned an empty response",
);
const chunks: Uint8Array[] = [];
let length = 0;
try {
for (;;) {
const next = await reader.read();
if (next.done) break;
length += next.value.length;
if (length > MAX_RESPONSE_BYTES)
throw new PhotonError(
"invalid_response",
"Photon project response exceeds the supported size",
);
chunks.push(next.value);
}
const body: unknown = JSON.parse(Buffer.concat(chunks).toString("utf8"));View on GitHub (pinned to 3f1d897a7c)