paperclipai/paperclip · error
Photon cannot edit a multipart publication
Error message
Photon cannot edit a multipart publication
What it means
Thrown in PhotonAdapter.publish when an edit (replaceMessageId set) would replace a message that splits into multiple text parts or carries attachments. iMessage edits can only change a single text bubble, so the adapter refuses multipart/attachment publications rather than producing an edit that cannot round-trip.
Solutions
- Ensure the replacement text fits in a single Photon text part (shorten the message below the split threshold)
- Send the correction as a brand-new publication without replaceMessageId instead of an edit
- Remove files from the edited message and publish attachments separately as a new publication
- Adjust the UI/flow so edits are only offered for single-part, text-only messages
Example fix
// before
await adapter.publish(id, pubId, { text: longText }, { replaceMessageId: msgId, ... });
// after
const parts = splitPhotonText(longText);
if (parts.length !== 1) {
await adapter.publish(id, pubId, { text: longText }, opts); // new publication
} else {
await adapter.publish(id, pubId, { text: longText }, { ...opts, replaceMessageId: msgId });
} Defensive patterns
Strategy: validation
Validate before calling
const parts = splitPhotonText(textOf(message));
if (opts.replaceMessageId && (parts.length !== 1 || message.files?.length))
throw new Error('Edits must be single-part text-only; publish as new publication instead'); Type guard
function isEditable(msg, replaceId) {
return Boolean(replaceId) && splitPhotonText(textOf(msg)).length === 1 && !(msg.files?.length);
} Try / catch
try {
await adapter.publish(id, pubId, msg, opts);
} catch (e) {
if (e.message === 'Photon cannot edit a multipart publication') {
delete opts.replaceMessageId;
return adapter.publish(id, pubId, msg, opts); // new publication
}
throw e;
} Prevention
- Only offer edit UI for messages that fit one Photon text part and have no attachments
- Split or truncate long text before planning an edit
- Never attach files to a replacement publication
- Centralize edit eligibility checks in one helper
When it happens
Trigger: publish(id, publicationId, message, { replaceMessageId, ... }) where splitPhotonText(textOf(message)) yields parts.length !== 1, or message.files is a non-empty array.
Common situations: Editing a long agent response that exceeds the Photon text-part limit and was split into multiple bubbles; adding files to what started as an edit; reusing a replace flow for a publication that grew over size thresholds.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- ACPX runtime does not expose session goal controls
- ACPX session goal action
- Environment driver " " does not support duplex channels.
- Environment driver " " does not support orphan sandbox…
- github_attachment_canonical_file_unsupported
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/f27c2cad6e01ad9e.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/adapter.ts:421
publicationId: string,
message: AdapterPostableMessage,
options: {
replyTo?: string;
replaceMessageId?: string;
retryUnknown?: boolean;
assertCurrent(): Promise<void>;
},
): Promise<{ id: string; messageIds: string[] }> {
const chat = await this.chatInfo(id);
if (chat.isArchived) throw new Error("Photon conversation is unavailable");
const text = textOf(message);
const files =
typeof message !== "string" && "files" in message
? (message.files ?? [])
: [];
const parts = splitPhotonText(text);
if (options.replaceMessageId && (parts.length !== 1 || files.length))
throw new Error("Photon cannot edit a multipart publication");
let lastId: string | undefined;
const messageIds: string[] = [];
for (let index = 0; index < parts.length; index++) {
lastId = await this.sendPart(
publicationId,
`text-${index}`,
{
chatGuid: chat.guid,
text: parts[index],
replyTo: options.replyTo,
replaceMessageId: options.replaceMessageId,
},
undefined,
options,
);
messageIds.push(lastId);
}
for (let index = 0; index < files.length; index++) {View on GitHub (pinned to 3f1d897a7c)