paperclipai/paperclip · error · PhotonError

rejected

rejected

Error message

Photon prompt changed after preparation

What it means

A PhotonError with code 'rejected' thrown from publishPhotonPrompt's identity update when a prompt digest already exists for the binding key and differs from the newly computed digest. This guarantees a prompt cannot silently change after preparation — republishing mutated content for the same identity is treated as a rejection to protect answer integrity.

Solutions

  1. Do not mutate the prompt after preparation; publish a new binding key/identity for changed content
  2. If the change is intentional, clear/rotate the `${key}:identity` state or create a fresh interaction
  3. Make prompt generation deterministic so retries produce the identical digest

Example fix

// before
const key = `prompt:${issueId}`;
await publishPhotonPrompt(adapter, key, changedInput);
// after
const key = `prompt:${issueId}:${hash(JSON.stringify(changedInput))}`;
await publishPhotonPrompt(adapter, key, changedInput);
Defensive patterns

Strategy: try-catch

Validate before calling

const digest = computeDigest(input); const current = await adapter.state.get(`${key}:identity`); if (current && current.digest !== digest) throw new Error('prompt changed after preparation');

Try / catch

try { await publishPhotonPrompt(adapter, key, input); } catch (e) { if (e instanceof PhotonError && e.code === 'rejected' && /changed after preparation/.test(e.message)) return republishUnderNewKey(input); throw e; }

Prevention

When it happens

Trigger: Calling publishPhotonPrompt with content that hashes to a different digest than the one stored at `${key}:identity`, i.e. the prompt text/questions for the same binding were modified after the first preparation.

Common situations: Editing the question text or options and republishing without a new binding key; a retry path recomputes the prompt with different formatting; concurrent publishers racing on the same binding with divergent content.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/photon/interactions.ts:169

  taskUrl?: string | null;
  retryUnknown?: boolean;
  assertCurrent(): Promise<void>;
}): Promise<PhotonPromptReceipt> {
  const { adapter, binding, interaction, questionIndex, assertCurrent } = input;
  const key = `prompt:${binding.reference}:${questionIndex}`;
  const digest = nativeSha256({
    binding,
    threadId: input.threadId,
    kind: interaction.kind,
    payload: interaction.payload,
    questionIndex,
    taskUrl: input.taskUrl ?? null,
  });
  await adapter.state.update<{ digest: string }>(
    `${key}:identity`,
    (current) => {
      if (current && current.digest !== digest)
        throw new PhotonError(
          "rejected",
          "Photon prompt changed after preparation",
        );
      return current ?? { digest };
    },
  );
  const existing = await adapter.state.read<PhotonPromptReceipt>(key);
  if (existing) {
    for (const guid of existing.promptMessageGuids ?? [
      existing.promptMessageGuid,
    ])
      await adapter.state.update<PhotonPromptReceipt>(
        `prompt-message:${guid}`,
        (current) => current ?? existing,
      );
    if (existing.pollMessageGuid)
      await adapter.state.update<PhotonPromptReceipt>(
        `poll-message:${existing.pollMessageGuid}`,

View on GitHub (pinned to 3f1d897a7c)