paperclipai/paperclip · error

Photon attachment belongs to another line

Error message

Photon attachment belongs to another line

What it means

downloadPhotonAttachment is called with a lineId argument and a locator object that itself carries a lineId. This guard (server/src/services/photon/attachments.ts:77) throws when the two disagree, i.e. the caller is trying to download an attachment attributed to a different chat line than the one it is operating on. It is a tenant/ownership cross-check that prevents cross-line attachment fetching.

Solutions

  1. Check the locator you pass: ensure locator.lineId equals the lineId argument in the same call.
  2. Re-derive the locator with photonAttachmentLocator(attachment, lineId, chatGuid, message) for the current line instead of reusing a stored locator.
  3. If the attachment truly belongs to another line, resolve that line's client and call with the correct lineId.
  4. Verify stored fetchMetadata was not migrated/copied across lines without updating lineId.

Example fix

// before
await downloadPhotonAttachment(client, currentLineId, staleLocator);
// after
if (staleLocator.lineId !== currentLineId) {
  locator = photonAttachmentLocator(attachment, currentLineId, chatGuid, message);
}
await downloadPhotonAttachment(client, currentLineId, locator);
Defensive patterns

Strategy: validation

Validate before calling

function canDownload(locator: PhotonAttachmentLocator, lineId: string): boolean {
  return photonAttachmentLocatorSchema.safeParse(locator).success && locator.lineId === lineId;
}

Type guard

function isForLine(locator: PhotonAttachmentLocator, lineId: string): locator is PhotonAttachmentLocator & { lineId: typeof lineId } {
  return locator.lineId === lineId;
}

Try / catch

try {
  await downloadPhotonAttachment(client, lineId, locator);
} catch (e) {
  if (e instanceof Error && e.message === "Photon attachment belongs to another line") {
    // rebuild locator for this line or fetch via the correct line's client
  } else throw e;
}

Prevention

When it happens

Trigger: Calling downloadPhotonAttachment(client, lineIdA, locator) where locator.lineId === lineIdB (locator.lineId !== lineId). Any caller that reuses a cached locator with the wrong line context triggers it.

Common situations: A caller fetched locators for multiple lines and mixed them up; an attachment row was re-parented or copied to another line without regenerating fetchMetadata; recovery/rehydration code passes a stale lineId after a line was re-created.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

  const raw = message.raw as PhotonMessage;
  if (
    !raw.chatGuids?.includes(chatGuid) ||
    !raw.content?.attachments.some(
      (candidate) => candidate.guid === parsed.data.attachmentGuid,
    )
  )
    return null;
  return parsed.data;
}
export async function downloadPhotonAttachment(
  client: GrpcAdvancedIMessage,
  lineId: string,
  locator: PhotonAttachmentLocator,
  allocation: "dedicated" | "shared" = "dedicated",
): Promise<Buffer> {
  photonAttachmentLocatorSchema.parse(locator);
  if (locator.lineId !== lineId)
    throw new Error("Photon attachment belongs to another line");
  const source = await client.messages
    .get(locator.messageGuid)
    .catch((error) => {
      throw photonFailure(error);
    });
  const attachment = source.content.attachments.find(
    (candidate) => candidate.guid === locator.attachmentGuid,
  );
  if (
    source.guid !== locator.messageGuid ||
    !source.chatGuids.includes(locator.chatGuid) ||
    !attachment ||
    attachment.isSticker ||
    attachment.isHidden
  )
    throw new Error(
      "Photon attachment does not belong to this message and chat",
    );

View on GitHub (pinned to 3f1d897a7c)