{"record":{"id":"0e9331ef4fce3fa0","repo":"paperclipai/paperclip","slug":"photon-attachment-metadata-changed","errorCode":null,"errorMessage":"Photon attachment metadata changed","messagePattern":"Photon attachment metadata changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/photon/attachments.ts","lineNumber":139,"sourceCode":"        // The shared gateway rewrites message/metadata attachment IDs to opaque\n        // project aliases, but streams the native UUID in download headers.\n        // Ownership comes from the authenticated source-message lookup above\n        // and this exact alias-addressed RPC, never from matching filenames.\n        const sharedAlias = allocation === \"shared\" &&\n          /^spc-att-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(locator.attachmentGuid);\n        const matchingSharedHeader = sharedAlias &&\n          /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(part.info.guid) &&\n          part.info.totalBytes === attachment.totalBytes &&\n          part.info.mimeType === attachment.mimeType &&\n          part.info.fileName === attachment.fileName;\n        if (\n          header ||\n          (part.info.guid !== locator.attachmentGuid && !matchingSharedHeader) ||\n          part.info.isHidden || part.info.isSticker ||\n          !Number.isSafeInteger(part.info.totalBytes) || part.info.totalBytes < 0 ||\n          part.info.totalBytes > MAX_ATTACHMENT_BYTES\n        )\n          throw new Error(\"Photon attachment metadata changed\");\n        header = true;\n        companionInfo = part.companionInfo;\n        companionUnavailable = Boolean(\n          companionInfo &&\n            (companionInfo.kind !== \"live-photo-video\" ||\n              ![\"video/quicktime\", \"video/mp4\"].includes(\n                companionInfo.mimeType,\n              ) ||\n              !Number.isSafeInteger(companionInfo.totalBytes) ||\n              companionInfo.totalBytes <= 0 ||\n              companionInfo.totalBytes > MAX_ATTACHMENT_BYTES),\n        );\n      } else if (part.type === \"primaryChunk\") {\n        if (companionStarted)\n          throw new Error(\"Photon attachment chunks arrived out of order\");\n        if (!header) throw new Error(\"Photon attachment header is missing\");\n        length += part.data.length;\n        if (length > MAX_ATTACHMENT_BYTES)","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/photon/attachments.ts#L121-L157","documentation":"When the download stream emits its header part, its metadata must match expectations exactly: it must be the first header, part.info.guid must equal locator.attachmentGuid (or match a validated shared-gateway alias), the part must not be hidden/sticker, and totalBytes must be a safe in-range integer. Any mismatch throws this error (server/src/services/photon/attachments.ts:139), because the streamed bytes would not correspond to the attachment requested.","triggerScenarios":"A second header part arrives (header already true); stream header guid differs from locator.attachmentGuid without a valid shared alias match (spc-att-* alias plus matching native UUID, totalBytes, mimeType, fileName); header part is hidden/sticker; header totalBytes is invalid or over the cap.","commonSituations":"Using a shared-gateway alias attachmentGuid where the gateway rewrote IDs and the alias-validation fields (mimeType/fileName/totalBytes) changed after the message lookup; gateway version mismatch emitting rewritten GUIDs in headers; retrying a stream that already delivered a header on a stale stream object.","solutions":["Re-fetch the message metadata and rebuild the locator so the attachmentGuid matches what the stream header reports.","If using allocation \"shared\", confirm the spc-att-* alias and that fileName/mimeType/totalBytes still match the message attachment metadata; otherwise use allocation \"dedicated\".","Upgrade/align the Photon gateway version so header GUIDs are native UUIDs consistent with message metadata.","Do not reuse a partially consumed download stream; open a fresh client.attachments.downloadStream per attempt."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const att = (message.raw as PhotonMessage).content.attachments.find(a => a.guid === locator.attachmentGuid);\nif (!att) throw new Error(\"no such attachment; header match will fail\");","typeGuard":null,"tryCatchPattern":"try {\n  await downloadPhotonAttachment(client, lineId, locator);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Photon attachment metadata changed\") {\n    // re-fetch message metadata, rebuild locator, retry once; else mark unavailable\n  } else throw e;\n}","preventionTips":["Refresh message metadata immediately before downloading so header info matches","Use dedicated allocation unless the shared alias fields are known to be stable","Never reuse a partially consumed download stream"],"tags":["streaming","metadata-mismatch","attachments"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}