paperclipai/paperclip · error

Photon attachment does not belong to this message and chat

Error message

Photon attachment does not belong to this message and chat

What it means

After fetching the source message via client.messages.get(locator.messageGuid), the code verifies the returned message's guid, its chat list, and that the attachment exists on it and is neither a sticker nor hidden. If any check fails, it throws that the attachment does not belong to this message and chat (server/src/services/photon/attachments.ts:94). It is a consistency/ownership guard against stale or forged locators.

Solutions

  1. Rebuild the locator from the current message with photonAttachmentLocator() so chatGuid, messageGuid, and attachmentGuid are consistent.
  2. Confirm the attachment is a regular (non-sticker, non-hidden) attachment on the message before downloading.
  3. Verify locator.messageGuid and locator.chatGuid match a message that still exists in the Photon chat store.
  4. For shared-gateway aliases, ensure the source-message lookup ran so ownership is established before the download RPC.

Example fix

// before
const locator = oldStoredLocator; // may point at edited/hidden attachment
await downloadPhotonAttachment(client, lineId, locator);
// after
const locator = photonAttachmentLocator(attachment, lineId, chatGuid, message);
if (!locator) throw new Error("attachment is not downloadable on this message");
await downloadPhotonAttachment(client, lineId, locator);
Defensive patterns

Strategy: validation

Validate before calling

const locator = photonAttachmentLocator(attachment, lineId, chatGuid, message);
if (!locator) throw new Error("attachment not downloadable on this message/chat");
const raw = message.raw as PhotonMessage;
const att = raw.content?.attachments.find(a => a.guid === locator.attachmentGuid);
if (!att || att.isSticker || att.isHidden) throw new Error("attachment hidden or sticker");

Type guard

function isDownloadable(att: Attachment | undefined): att is Attachment {
  return !!att && !att.isSticker && !att.isHidden;
}

Try / catch

try {
  await downloadPhotonAttachment(client, lineId, locator);
} catch (e) {
  if (e instanceof Error && e.message.includes("does not belong to this message and chat")) {
    // refresh message metadata and rebuild the locator, or skip the attachment
  } else throw e;
}

Prevention

When it happens

Trigger: The fetched message's guid differs from locator.messageGuid; locator.chatGuid is not in source.chatGuids; no attachment with locator.attachmentGuid exists on the message; or the matching attachment is flagged isSticker or isHidden.

Common situations: Stale locator pointing at a message that was edited or deleted and re-created; attachment was a sticker or hidden asset that later got filtered; GUID casing/format mismatch between gateway aliases and native UUIDs; locator built from a different message than the one fetched.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

  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",
    );
  if (
    !Number.isSafeInteger(attachment.totalBytes) ||
    attachment.totalBytes < 0 ||
    attachment.totalBytes > MAX_ATTACHMENT_BYTES
  )
    throw new Error("Attachment exceeds the configured size limit");
  const stream = client.attachments.downloadStream(locator.attachmentGuid);
  let timedOut = false;
  const timer = setTimeout(() => {
    timedOut = true;
    void stream.close();
  }, 30_000);
  timer.unref();
  const chunks: Uint8Array[] = [];
  let length = 0;
  let header = false;

View on GitHub (pinned to 3f1d897a7c)