paperclipai/paperclip · error · Error

paperclip_runner_tool_idempotency_conflict

paperclip_runner_tool_idempotency_conflict

Error message

paperclip_runner_tool_idempotency_conflict

What it means

Thrown from the create-issue/create-child runner tool path when the issue service reports the write was deduplicated (an existing issue matched the origin fingerprint), but the resulting child's originFingerprint differs from the caller-supplied fingerprint. This indicates the idempotency key collided with a prior, materially different creation — the same key was reused for different inputs.

Solutions

  1. Generate a fresh, unique idempotencyKey for the new logical creation instead of reusing the old key.
  2. If a retry was intended, resend the exact same input payload (byte-identical after canonicalization) with the same key so dedupe replays the original result.
  3. Inspect the existing child issue found by dedupe to confirm whether the prior creation already covers the intent; if so, adopt it rather than creating another.

Example fix

// before
await createTask({ idempotencyKey: key, title: 'New title' }); // key used for 'Old title'

// after: same key => same payload, or new key for new intent
await createTask({ idempotencyKey: newIdempotencyKey(), title: 'New title' });
Defensive patterns

Strategy: validation

Validate before calling

if (!isUuid(key) && key.length > 200) throw new Error('bad idempotencyKey');
// ensure one key is used for exactly one logical creation and payload

Prevention

When it happens

Trigger: Reusing an idempotencyKey from a previous create task call while changing the task payload (title/description/blockedBy etc.) so the dedupe layer matches an older record by key but the persisted fingerprint no longer equals the new inputFingerprint.

Common situations: Agent frameworks that auto-generate stable keys per logical step but mutate step contents on retry; hand-rolled retry code that caches the key but regenerates the body; template-driven delegation that reuses keys across runs.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/8bb1dc1e60ade28a. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/native-runtime/paperclip-runner-tool-authority.ts:819

        blockedByIssueIds,
        blockParentUntilDone: false,
        createdByAgentId: this.binding.agentId,
        originKind: "manual" as const,
        originId: durableIdempotencyKey,
        originRunId: this.binding.runId,
        originIdentityContextId: identityContextId,
        continuationIdentityContextId: identityContextId,
        originFingerprint: inputFingerprint,
        actorAgentId: this.binding.agentId,
        actorRunId: this.binding.runId,
        idempotencyKey: durableIdempotencyKey,
        onDeduplicated: () => { deduplicated = true; },
      };
      const child = conversation
        ? await issueService(tx).create(this.binding.companyId, createInput)
        : (await issueService(tx).createChild(this.binding.issueId, createInput)).issue;
      if (deduplicated && child.originFingerprint !== inputFingerprint) {
        throw new Error("paperclip_runner_tool_idempotency_conflict");
      }
      let childStatus = child.status;
      let childStatusVersion = child.statusVersion;
      if (child.status === "blocked" && blockedByIssueIds.length > 0) {
        const readiness = await issueService(tx).getDependencyReadiness(child.id, tx);
        if (readiness.isDependencyReady) {
          const readyChild = await issueService(tx).update(child.id, {
            status: "todo",
            actorAgentId: this.binding.agentId,
          }, tx);
          if (readyChild) {
            childStatus = readyChild.status;
            childStatusVersion = readyChild.statusVersion;
          }
        }
      }
      if (!deduplicated) {
        const activity = await persistActivity(tx, {

View on GitHub (pinned to 3f1d897a7c)