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

  1. 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.
  2. 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).
  3. Inspect adapter state for key `poll:<publicationId>:<questionIndex>` and manually reset it to {schema:1, phase:"prepared"} if the poll was never created.
  4. 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

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


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)