paperclipai/paperclip · error
Photon send identity changed
Error message
Photon send identity changed
What it means
Thrown inside the save() helper in sendPart when state.update finds either no existing SendRecord for the key or one whose digest differs from the digest captured when this attempt started. It protects the read-modify-write cycle of the send state machine: if the record disappeared or was replaced mid-flight (payload identity changed under us), persisting a phase patch would be unsafe.
Solutions
- Ensure only one publisher owns a publicationId at a time (lock or single-flight per publication)
- Never mutate message content between sendPart attempts — mint a new publicationId for new content
- Restore or re-seed the state store if the record was deleted by accident, then restart the publication from scratch
- Check for multiple server instances sharing state with divergent payloads
Example fix
// before // two calls racing on the same pubId publish(id, pubId, msgA, opts); publish(id, pubId, msgB, opts); // after await withPublicationLock(pubId, () => publish(id, pubId, msgA, opts)); // serialize; use pubId' for msgB
Defensive patterns
Strategy: try-catch
Try / catch
try {
await adapter.publish(id, pubId, msg, opts);
} catch (e) {
if (e.message === 'Photon send identity changed') {
log.error('concurrent publisher or state reset on ' + pubId);
abortPublication(pubId); // do not retry blindly
}
throw e;
} Prevention
- Single-flight/lock publishing per publicationId
- Run only one server instance owning a given publication's state
- Never clear the state store while sends are in flight
- Never mutate payload content between sendPart attempts
When it happens
Trigger: Concurrent sendPart calls for the same `send:{publicationId}:{part}` key where one overwrote the record; another process cleared or re-keyed the state store between the initial update and a later save(); the payload was mutated so its digest no longer matches the stored record while the send is in progress.
Common situations: Two workers racing to publish the same publicationId; an admin reset wiped state mid-send; shared state store (e.g. DB-backed) with another instance publishing different content under the same id; retry loop that mutates message text between attempts.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Bridge envelope changed while reading.
- Capability live turn admission was abandoned during teardown
- Capability live turn start completed after admission…
- CreateOS lease cleanup is already in progress.
- native_replacement_stopped_session_changed
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/af5c2a0f293ba8ce.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/adapter.ts:501
let record = await this.state.update<SendRecord>(key, (current) => {
if (current && current.digest !== digest)
throw new Error("Photon publication payload changed after preparation");
return current ?? { schema: 1, digest, phase: "prepared" };
});
if (record.phase === "sent" && record.messageGuid)
return record.messageGuid;
if (
(record.phase === "sending" || record.phase === "uploading") &&
!options.retryUnknown
)
throw new PhotonError(
"delivery_unknown",
"Photon delivery is unknown; resolve this publication before retrying",
);
const save = async (patch: Partial<SendRecord>) => {
record = await this.state.update<SendRecord>(key, (current) => {
if (!current || current.digest !== digest)
throw new Error("Photon send identity changed");
return { ...current, ...patch };
});
};
if (file && !record.attachmentGuid) {
await options.assertCurrent();
await save({ phase: "uploading" });
try {
const uploaded = await this.client.attachments.upload({
fileName: file.filename,
data: file.data as Buffer,
});
if (!uploaded.attachment.guid)
throw new PhotonError(
"delivery_unknown",
"Photon upload receipt is missing",
);
await save({
phase: "uploaded",View on GitHub (pinned to 3f1d897a7c)