paperclipai/paperclip · error · PhotonError
delivery_unknown
delivery_unknown
Error message
Photon poll creation is unknown; reconcile this publication before retrying
What it means
publishPhotonPrompt persists a per-poll state record (`poll:<publicationId>:<questionIndex>`) with phases prepared -> creating -> created. If a previous attempt recorded phase "creating" (meaning a polls.create call was dispatched but its outcome is unknown) and the caller has not opted into retryUnknown, the function refuses to blindly re-send and throws this PhotonError with code delivery_unknown. It exists to prevent duplicate poll delivery when the outcome of the prior Photon API call was never confirmed.
Solutions
- Run the reconciliation flow for this publication (confirm whether the poll actually exists in Photon), then either advance state to created/prepared or clear it.
- Retry the publication with input.retryUnknown set to true once you have determined re-sending is safe (the clientMessageId hash dedupes on the Photon side).
- Inspect adapter state for key `poll:<publicationId>:<questionIndex>` and manually reset it to {schema:1, phase:"prepared"} if the poll was never created.
- If the create call is known to have failed with a definitive (non-unknown) error, the catch handler should have reset the phase; investigate why state still says creating.
Example fix
// before
await publishPhotonPrompt({ threadId, retryUnknown: false, ... });
// after — after reconciling that the poll was not delivered
await publishPhotonPrompt({ threadId, retryUnknown: true, ... }); Defensive patterns
Strategy: retry
Validate before calling
const stateKey = `poll:${publicationId}:${questionIndex}`;
const saved = await adapter.state.read<{phase:string}>(stateKey);
const safeToPublish = !saved || saved.phase === "prepared" || saved.phase === "created" || retryUnknown; Type guard
function isResumablePollState(s: {phase: string} | null | undefined, retryUnknown: boolean): boolean {
return !s || s.phase === "prepared" || s.phase === "created" || retryUnknown;
} Try / catch
try {
await publishPhotonPrompt(input);
} catch (e) {
if (e instanceof PhotonError && e.code === "delivery_unknown") {
await reconcilePublication(input.publicationId); // confirm with Photon, reset state
await publishPhotonPrompt({ ...input, retryUnknown: true });
} else throw e;
} Prevention
- After any unknown-outcome create failure, run reconciliation before the next attempt.
- Always pass the same clientMessageId so Photon-side dedupe protects retries.
- Serialize publishing per publicationId to avoid racing state transitions.
- Monitor for state records stuck in phase creating and alert on them.
When it happens
Trigger: Calling publishPhotonPrompt (via first/second/receipt paths) when adapter.state.read(stateKey) returns a PollSend with phase "creating" — i.e. a prior create attempt crashed, timed out, or threw an opaque network error after marking creating — and input.retryUnknown is falsy.
Common situations: Process restart or crash between marking phase creating and receiving the polls.create response; an unhandled delivery_unknown from a prior run; a retry driver that does not pass retryUnknown after an unknown-outcome failure; stale state left from an earlier deployment of the same publicationId.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Photon poll already claimed
- Completed warm transition template conflicts with its exact…
- Warm run transition template conflicts with its exact…
- A different semantic result was already committed
- Cannot build API path with an empty path segment.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/05a7044a8911d9e6.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/photon/interactions.ts:283
publicationId: binding.publicationId,
questionIndex,
promptMessageGuid: prompt.id,
promptMessageGuids: prompt.messageIds,
options: {},
};
if (nativePoll) {
const stateKey = `poll:${binding.publicationId}:${questionIndex}`;
type PollSend = {
schema: 1;
phase: "prepared" | "creating" | "created";
receipt?: {
pollMessageGuid: string;
options: Array<{ optionIdentifier: string; text: string }>;
};
};
let saved = await adapter.state.read<PollSend>(stateKey);
if (saved?.phase === "creating" && !input.retryUnknown)
throw new PhotonError(
"delivery_unknown",
"Photon poll creation is unknown; reconcile this publication before retrying",
);
if (!saved || saved.phase !== "created") {
await assertCurrent();
await adapter.state.update<PollSend>(stateKey, (current) => {
if (current?.phase === "creating" && !input.retryUnknown)
throw new PhotonAnswerValidationError("Photon poll already claimed");
return { schema: 1, phase: "creating" };
});
const poll = await adapter.client.polls
.create(
adapter.decodeThreadId(input.threadId).chatGuid,
title,
choices.map((choice) => choice.label),
{
clientMessageId: createHash("sha256")
.update(`${adapter.state.scope.endpointId}:${stateKey}`)View on GitHub (pinned to 3f1d897a7c)