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
- Do not mutate the prompt after preparation; publish a new binding key/identity for changed content
- If the change is intentional, clear/rotate the `${key}:identity` state or create a fresh interaction
- 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
- Treat prepared prompts as immutable; version binding keys with a content hash
- Make prompt generation deterministic for retries
- Never edit question text/options in place after preparation
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
- ACPX runtime executable digest mismatch
- ACPX private snapshot digest mismatch
- ACPX provider requires verified package snapshots
- ACPX snapshot manifest digest mismatch
- Artifact bytes do not match their immutable pin.
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)