paperclipai/paperclip · error

Durable PRP run attachment template conflicts.

Error message

Durable PRP run attachment template conflicts.

What it means

persistRunAttachTemplate() is idempotent for identical templates but fails closed when a different template is presented after one is already established in the durable store. Because the seed may be the only source for a later run.attach (command history can be compacted), silently overwriting it would be unsafe, so any canonical-JSON mismatch is rejected.

Source

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

  /**
   * Retain the connection-free provider preparation payload before the first
   * runner bootstrap. Completed command history is bounded and may be
   * compacted before a warm continuation arrives, so it cannot be the sole
   * source for a later run.attach. Repeating the same write is idempotent;
   * changing an established seed fails closed.
   */
  persistRunAttachTemplate(runAttachTemplate: Record<string, unknown>): void {
    if (!isRecord(runAttachTemplate.provider)) {
      throw new Error("Durable PRP run attachment template is invalid.");
    }
    const existing = this.#store.state.runAttachTemplate;
    if (
      existing !== undefined &&
      existing !== null &&
      canonicalJson(existing) !== canonicalJson(runAttachTemplate)
    ) {
      throw new Error("Durable PRP run attachment template conflicts.");
    }
    if (existing !== undefined && existing !== null) return;
    this.#store.state.runAttachTemplate = structuredClone(runAttachTemplate);
    this.#store.save();
  }

  issueBootstrapTicket(ttlMs = 5_000): string {
    this.#store.assertWritable();
    if (this.#store.state.warmTransition) {
      throw new Error(
        "Warm transition recovery requires its explicit one-use bootstrap capability.",
      );
    }
    if (!Number.isInteger(ttlMs) || ttlMs < 1_000 || ttlMs > 60_000) {
      throw new Error("Durable PRP bootstrap TTL is invalid.");
    }
    this.#pruneCredentials();
    const ticket = `bootstrap_${randomUUID()}`;

View on GitHub (pinned to 01ad858492)

Solutions

  1. Reuse the exact already-persisted template (read store.state.runAttachTemplate) instead of rebuilding it.
  2. Remove volatile fields (timestamps, nonces, request ids) from the template so repeated construction is byte-stable.
  3. If the template legitimately must change, follow the defined reset/re-rotation procedure rather than re-seeding.
  4. Diff canonicalJson(existing) vs canonicalJson(incoming) to find the drifted field before retrying.

Example fix

// before
controlPlane.persistRunAttachTemplate({ provider: { name: "anthropic" }, requestedAt: new Date().toISOString() }); // volatile field
// after
controlPlane.persistRunAttachTemplate({ provider: { name: "anthropic" } }); // stable payload
Defensive patterns

Strategy: validation

Validate before calling

const existing = store.state.runAttachTemplate;
if (existing != null && canonicalJson(existing) !== canonicalJson(template)) {
  template = structuredClone(existing); // reuse established seed instead of failing
}

Try / catch

try {
  controlPlane.persistRunAttachTemplate(template);
} catch (err) {
  if (err instanceof Error && err.message.includes("run attachment template conflicts")) {
    // already seeded with a different template — adopt the durable one
    template = structuredClone(store.state.runAttachTemplate);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling persistRunAttachTemplate() a second time (after a successful seed) with a template whose canonical JSON differs — any added, removed, or changed key/value, including key content in the provider object.

Common situations: Provider config (model, version, options) changed between process restarts; the caller recomputes the template with volatile fields (timestamps, request ids) embedded; two code paths seed the template with slightly different payloads; environment variable drift between runs.

Related errors


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