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

  1. Ensure the replacement text fits in a single Photon text part (shorten the message below the split threshold)
  2. Send the correction as a brand-new publication without replaceMessageId instead of an edit
  3. Remove files from the edited message and publish attachments separately as a new publication
  4. 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

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


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)