paperclipai/paperclip · error

Warm run transition target conflicts with its durable receip

Error message

Warm run transition target conflicts with its durable receipt.

What it means

rotateRunIdentity() was called with an identity whose canonical JSON does not equal the newIdentity recorded in the durable warm transition receipt. The transition receipt is the authoritative record of the target identity, so any mismatched rotation attempt is rejected to prevent hijacking the transition to a different identity.

Source

Thrown at packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts:1643

      if (
        runAttachTemplate !== undefined &&
        canonicalJson(runAttachTemplate) !== canonicalJson(template)
      ) {
        throw new Error(
          "Completed warm transition template conflicts with its exact command.",
        );
      }
      return;
    }
    const transition = this.#store.state.warmTransition;
    if (transition) {
      if (transition.phase === "awaiting_result")
        throw new Error("Warm transition result is not yet authenticated.");
      if (
        canonicalJson(identity) !==
        canonicalJson(transition.receipt.newIdentity)
      ) {
        throw new Error(
          "Warm run transition target conflicts with its durable receipt.",
        );
      }
      // The new authenticated peer, not an attach-result observer, owns the
      // activation boundary. Keep the old credential and command replay lane.
      if (runAttachTemplate !== undefined) {
        const { paperclipNextAuthority: _boundary, ...expectedTemplate } =
          transition.command.payload;
        if (
          canonicalJson(runAttachTemplate) !== canonicalJson(expectedTemplate)
        ) {
          throw new Error(
            "Warm run transition template conflicts with its exact command.",
          );
        }
        const candidate = structuredClone(this.#store.state);
        candidate.runAttachTemplate = structuredClone(runAttachTemplate);
        this.#store.commit(candidate);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Use exactly the identity from warmTransition.receipt.newIdentity when calling rotateRunIdentity during a transition.
  2. If a different target identity is truly wanted, cancel/reset the existing transition via the recovery path first.
  3. Persist and reload the receipt identity rather than recomputing it from local config.
  4. Log canonicalJson(identity) vs transition.receipt.newIdentity to find the differing field.

Example fix

// before
controlPlane.rotateRunIdentity({ ...newIdentity, runId: freshRunId() }); // conflicts with receipt
// after
const receipt = store.state.warmTransition.receipt;
controlPlane.rotateRunIdentity(structuredClone(receipt.newIdentity));
Defensive patterns

Strategy: validation

Validate before calling

const t = store.state.warmTransition;
if (t && canonicalJson(identity) !== canonicalJson(t.receipt.newIdentity)) {
  identity = structuredClone(t.receipt.newIdentity); // adopt receipt identity
}

Type guard

function matchesReceipt(identity: DurableRecoveryIdentity, t: { receipt: { newIdentity: DurableRecoveryIdentity } }): boolean {
  return canonicalJson(identity) === canonicalJson(t.receipt.newIdentity);
}

Try / catch

try {
  controlPlane.rotateRunIdentity(identity);
} catch (err) {
  if (err instanceof Error && err.message.includes("target conflicts with its durable receipt")) {
    controlPlane.rotateRunIdentity(structuredClone(store.state.warmTransition.receipt.newIdentity));
  } else throw err;
}

Prevention

When it happens

Trigger: Calling rotateRunIdentity(identity) while an authenticated warmTransition exists, passing an identity with different runnerInstanceId, environmentLeaseId, normalizedSessionId, or runId than transition.receipt.newIdentity.

Common situations: The caller regenerated runId between transition initiation and rotation; stale cached identity from a previous process; two supervisors racing with different target identities; config change altered a lease/session id mid-transition.

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@01ad858492 (2026-09-10). Data as JSON: /api/errors/b03385c67e8f4673. Report an issue: GitHub.