paperclipai/paperclip · error · Error

paperclip_runner_file_handoff_origin_not_persisted

paperclip_runner_file_handoff_origin_not_persisted

Error message

paperclip_runner_file_handoff_origin_not_persisted

What it means

After createAttachment persists the deliverable, the code confirms the database round-trip: the returned attachment's originatingRunId must equal the binding runId and an artifactWorkProductId must have been created. If either is absent, the origin/run provenance was not persisted, so the idempotency and rollback machinery cannot work; the upload is rolled back (storage object deleted) and this error is thrown.

Solutions

  1. Run pending database migrations (pnpm db:generate / apply) so issue_attachments.originating_run_id and the artifact work-product linkage exist and are populated.
  2. Check the createAttachment implementation in server issue service: ensure createdByRunId is written to originatingRunId and an artifact work product is created in the same transaction.
  3. Rebuild/align packages/db dist schema with the running server version; stale compiled schema can silently drop new columns.
  4. Verify no DB triggers/defaults null out originating_run_id or artifact_work_product_id on insert.

Example fix

// before (custom createAttachment)
await db.insert(issueAttachments).values({ issueId, provider, objectKey, contentType, byteSize, sha256, originalFilename, createdByAgentId });
// after
await db.insert(issueAttachments).values({ issueId, provider, objectKey, contentType, byteSize, sha256, originalFilename, createdByAgentId, createdByRunId: runId, artifactWorkProductId: await ensureArtifactWorkProduct(...) });
Defensive patterns

Strategy: validation

Validate before calling

// confirm the DB schema supports run provenance before enabling native file handoff
const cols = await db.execute(sql`SELECT column_name FROM information_schema.columns WHERE table_name = 'issue_attachments'`);
const names = cols.rows.map(r => r.column_name);
if (!names.includes('originating_run_id')) throw new Error('run pending migration: originating_run_id missing');

Try / catch

try {
  await prepareNativeRunnerFileHandoff(input);
} catch (e) {
  if (e.message === 'paperclip_runner_file_handoff_origin_not_persisted') {
    await runPendingMigrations(); // the catch path already deleted the uploaded object
    return prepareNativeRunnerFileHandoff(input);
  }
  throw e;
}

Prevention

When it happens

Trigger: issueService.createAttachment returns an attachment whose originatingRunId differs from input.binding.runId, or whose artifactWorkProductId is null/undefined — e.g. createAttachment ignoring createdByRunId, or a fork/older schema lacking the originating_run_id or artifact_work_product_id columns.

Common situations: Running server code against a database missing recent migrations that added originating_run_id or artifact work-product linkage; a modified createAttachment that drops run/agent metadata; schema drift between packages/db and the running server build; trims/defaults overwriting runId.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/native-runtime/native-runner-file-handoff.ts:1260

    ) {
      throw new Error("paperclip_runner_file_handoff_storage_mismatch");
    }
    const attachment = await issueService(input.db).createAttachment({
      issueId: input.binding.issueId,
      provider: stored.provider,
      objectKey: stored.objectKey,
      contentType: stored.contentType,
      byteSize: stored.byteSize,
      sha256: stored.sha256,
      originalFilename: stored.originalFilename,
      createdByAgentId: input.binding.agentId,
      createdByRunId: input.binding.runId,
    });
    if (
      attachment.originatingRunId !== input.binding.runId ||
      !attachment.artifactWorkProductId
    ) {
      throw new Error("paperclip_runner_file_handoff_origin_not_persisted");
    }
    await input.db
      .update(issueWorkProducts)
      .set({ title: verified.title, updatedAt: new Date() })
      .where(
        and(
          eq(issueWorkProducts.id, attachment.artifactWorkProductId),
          eq(issueWorkProducts.companyId, input.binding.companyId),
          eq(issueWorkProducts.issueId, input.binding.issueId),
          eq(issueWorkProducts.createdByRunId, input.binding.runId),
        ),
      );
    const comment = await issueService(input.db).addComment(
      input.binding.issueId,
      `Prepared ${verified.title} for this response.`,
      { agentId: input.binding.agentId, runId: input.binding.runId },
      {
        attachmentIds: [attachment.id],

View on GitHub (pinned to 3f1d897a7c)