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
- Run pending database migrations (pnpm db:generate / apply) so issue_attachments.originating_run_id and the artifact work-product linkage exist and are populated.
- 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.
- Rebuild/align packages/db dist schema with the running server version; stale compiled schema can silently drop new columns.
- 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
- Apply packages/db migrations before deploying server code that reads new columns.
- Keep createAttachment writing createdByRunId and creating the artifact work product atomically.
- Add a smoke test asserting attachment.originatingRunId === runId and artifactWorkProductId after creation.
- Avoid custom forks of createAttachment that drop provenance fields.
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
- Discord command registration persistence unproven
- paperclip_runner_file_handoff_existing_binding_invalid
- paperclip_runner_file_handoff_work_product_missing
- ACPX identity permission mode is invalid
- ACPX_PERSISTED_SESSION_MISSING
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)