paperclipai/paperclip · warning · PhotonError

attachment_not_ready

attachment_not_ready

Error message

Photon attachment is still being prepared; retry download

What it means

After the stream ends, the code checks for timeout, presence of a header, and at least one byte of primary data. If the 30s timer fired, no header arrived, or zero bytes were received, it throws a PhotonError with code attachment_not_ready (server/src/services/photon/attachments.ts:173). This signals the attachment is not yet downloadable (still being prepared server-side) rather than a hard failure.

Solutions

  1. Retry the download after a short backoff (e.g. 1-5s, a few attempts) — this error is explicitly retryable.
  2. Delay first download attempt for very fresh messages to let Photon finish preparing the attachment.
  3. Increase tolerance for slow gateways (the 30s timeout is fixed in this function; schedule retries around it).
  4. Check gateway health/connectivity if every attempt times out with zero bytes.

Example fix

// before
const body = await downloadPhotonAttachment(client, lineId, locator); // throws when not ready
// after
let body;
for (let attempt = 0; attempt < 4; attempt++) {
  try { body = await downloadPhotonAttachment(client, lineId, locator); break; }
  catch (e) {
    if (e instanceof PhotonError && e.code === "attachment_not_ready") {
      await new Promise(r => setTimeout(r, 2000 * (attempt + 1)));
      continue;
    }
    throw e;
  }
}
Defensive patterns

Strategy: retry

Try / catch

try {
  await downloadPhotonAttachment(client, lineId, locator);
} catch (e) {
  if (e instanceof PhotonError && e.code === "attachment_not_ready") {
    await sleep(backoffMs); // exponential, bounded attempts
    return retryDownload(locator);
  }
  throw e;
}

Prevention

When it happens

Trigger: stream.close() fired by the 30s timeout before data completed; stream ended with no header part; stream ended with length === 0.

Common situations: Recipient just sent a large photo and Photon has not finished ingesting it; gateway is slow or cold-starting; network stall triggering the 30s timeout; immediately reacting to a new-message event before the attachment is ready.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/9be014b2bbd74f51. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/photon/attachments.ts:173

        if (!header) throw new Error("Photon attachment header is missing");
        length += part.data.length;
        if (length > MAX_ATTACHMENT_BYTES)
          throw new Error("Attachment exceeds the configured size limit");
        chunks.push(part.data);
      } else if (part.type === "companionChunk") {
        if (!header || !companionInfo)
          throw new Error("Photon companion metadata is missing");
        companionStarted = true;
        companionLength += part.data.length;
        if (companionUnavailable || companionLength > MAX_ATTACHMENT_BYTES) {
          companionUnavailable = true;
          break;
        }
        companionChunks.push(part.data);
      }
    }
    if (timedOut || !header || !length)
      throw new PhotonError(
        "attachment_not_ready",
        "Photon attachment is still being prepared; retry download",
      );
    if (attachment.totalBytes > 0 && length !== attachment.totalBytes)
      throw new PhotonError(
        "attachment_not_ready",
        "Photon attachment transfer is incomplete",
      );
    if (
      companionInfo &&
      !companionUnavailable &&
      companionLength !== companionInfo.totalBytes
    )
      throw new PhotonError(
        "attachment_not_ready",
        "Photon Live Photo companion is still being prepared",
      );
    const body = Buffer.concat(chunks);

View on GitHub (pinned to 3f1d897a7c)