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
- Reuse the exact already-persisted template (read store.state.runAttachTemplate) instead of rebuilding it.
- Remove volatile fields (timestamps, nonces, request ids) from the template so repeated construction is byte-stable.
- If the template legitimately must change, follow the defined reset/re-rotation procedure rather than re-seeding.
- 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
- Remove volatile fields (timestamps, ids, nonces) from templates so rebuilds are byte-stable.
- Load the persisted template on startup and reuse it rather than re-deriving from config.
- Version provider config deliberately; changing it should go through a defined reset flow, not re-seeding.
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
- Completed warm transition template conflicts with its exact
- Warm run transition template conflicts with its exact comman
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
- Challenge secret is required. Pass --token or --token-env.
- Cannot build API path with an empty path segment.
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/dc1b3edea5ab281f.
Report an issue: GitHub.