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

  1. Use the default global fetch (or a mock that returns a real Response with a string/stream body)
  2. Verify any custom fetchImpl passed to new PhotonCloudClient(fetchImpl) returns responses with a non-null body
  3. Check intermediary proxies/load balancers that might strip response bodies
  4. 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

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


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)