paperclipai/paperclip · error

Completed warm transition template conflicts with its exact

Error message

Completed warm transition template conflicts with its exact command.

What it means

rotateRunIdentity() was called on a control plane whose warm transition already completed durably (completedWarmTransition is set). For an idempotent replay the caller re-passed a runAttachTemplate whose canonical JSON differs from the template recorded in the completed transition's command payload (after stripping the paperclipNextAuthority boundary field). The library fails closed because the replay does not match the exact original command.

Source

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

  rotateRunIdentity(
    identity: DurableRecoveryIdentity,
    runAttachTemplate?: Record<string, unknown>,
  ): void {
    if (this.#protocolIntegrityError !== null)
      throw this.#protocolIntegrityError;
    const completed = this.#store.state.completedWarmTransition;
    if (
      completed &&
      canonicalJson(identity) === canonicalJson(this.#identity) &&
      canonicalJson(identity) === canonicalJson(completed.receipt.newIdentity)
    ) {
      const { paperclipNextAuthority: _boundary, ...template } =
        completed.command.payload;
      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

View on GitHub (pinned to 01ad858492)

Solutions

  1. Do not pass runAttachTemplate on idempotent replays of a completed warm transition; call rotateRunIdentity(identity) with the template omitted.
  2. Compare your template against the completed transition's command payload (minus paperclipNextAuthority) and reuse the exact original object.
  3. If the template genuinely must change, treat it as a new rotation: reset the transition state per your recovery procedure instead of replaying the completed one.
  4. Log canonicalJson(runAttachTemplate) and the completed command payload to identify which field drifted before retrying.

Example fix

// before
controlPlane.rotateRunIdentity(newIdentity, { provider: { name: "claude" }, model: "opus" }); // replay after completion, template drifted
// after
controlPlane.rotateRunIdentity(newIdentity); // idempotent replay without re-asserting the template
Defensive patterns

Strategy: try-catch

Validate before calling

// before replaying a completed transition
const completed = store.state.completedWarmTransition;
if (completed && runAttachTemplate !== undefined) {
  const { paperclipNextAuthority: _b, ...expected } = completed.command.payload;
  if (canonicalJson(runAttachTemplate) !== canonicalJson(expected)) {
    runAttachTemplate = undefined; // omit on idempotent replay
  }
}

Try / catch

try {
  controlPlane.rotateRunIdentity(identity, runAttachTemplate);
} catch (err) {
  if (err instanceof Error && err.message.includes("Completed warm transition template conflicts")) {
    controlPlane.rotateRunIdentity(identity); // replay without re-asserting template
  } else throw err;
}

Prevention

When it happens

Trigger: Calling rotateRunIdentity(identity, runAttachTemplate) a second time with the same identity after the transition completed, but with a modified runAttachTemplate (different keys, values, ordering-insensitive but content-different) than the one used in the original, already-completed transition.

Common situations: Caller code recomputes the attach template from drifted config (e.g. provider settings changed between retries); a serialization layer adds or drops fields; a retry path passes an updated template while the transition already succeeded on the first attempt.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/23a3109c2162d0b3. Report an issue: GitHub.