paperclipai/paperclip · error · Error
paperclip_runner_file_handoff_existing_binding_invalid
paperclip_runner_file_handoff_existing_binding_invalid
Error message
paperclip_runner_file_handoff_existing_binding_invalid
What it means
When a duplicate handoff attachment is found bound to a comment, the code re-validates that the bound comment still exists with the exact expected author, run, and body (`Prepared <title> for this response.`) and is not soft-deleted. If the comment row is missing or no longer matches, the pre-existing binding is considered corrupt and this error is thrown instead of returning the duplicate disposition.
Solutions
- Inspect the issueComments row for existing.commentId; if it was soft-deleted or its body changed, restore the canonical body (`Prepared <title> for this response.`) and clear deletedAt, or null out the attachment's issueCommentId so a fresh comment is created.
- If the binding is unrecoverable, detach the attachment (set issueCommentId null) and re-run the handoff so it takes the create path.
- Check for code paths or admin tools that edit/delete agent comments and exclude comments created by the runner protocol (authorType 'agent', authorizationReason 'paperclip_runner_protocol').
- Verify the duplicate-match query filters (authorAgentId, createdByRunId, exact body) against your data; align the stored comment with what the current code generates.
Defensive patterns
Strategy: validation
Validate before calling
// pre-check the duplicate binding's comment before calling the handoff
const [c] = await db.select().from(issueComments).where(and(
eq(issueComments.id, existingCommentId),
isNull(issueComments.deletedAt),
eq(issueComments.authorType, 'agent'),
eq(issueComments.createdByRunId, runId),
));
if (!c) throw new Error('comment binding invalid; detach attachment before retry'); Try / catch
try {
return await prepareNativeRunnerFileHandoff(input);
} catch (e) {
if (e.message === 'paperclip_runner_file_handoff_existing_binding_invalid') {
await db.update(issueAttachments).set({ issueCommentId: null }).where(eq(issueAttachments.id, attachmentId));
return prepareNativeRunnerFileHandoff(input); // now takes the create path
}
throw e;
} Prevention
- Do not edit or soft-delete agent comments created under authorizationReason 'paperclip_runner_protocol'.
- If moderation must remove a comment, also null the attachment's issueCommentId in the same transaction.
- Keep the canonical 'Prepared <title> for this response.' body format stable across versions.
- Alert on attachments whose issueCommentId points to missing/deleted comments.
When it happens
Trigger: The attachment from the same run/filename/hash has a non-null issueCommentId, but the referenced issueComments row is deleted (deletedAt set), was authored by a different agent/run, or its body was edited so it no longer equals the canonical prepared-deliverable message.
Common situations: A user or moderation flow edited or soft-deleted the agent's 'Prepared ...' comment; a run retry after partial cleanup where the comment was removed but the attachment kept its commentId; data migrations that rewrote comment bodies; comment author recorded with a different runId due to retry re-binding.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- paperclip_runner_file_handoff_work_product_missing
- paperclip_runner_file_handoff_origin_not_persisted
- A different semantic result was already committed
- ACPX_PERSISTED_SESSION_IDENTITY_MISMATCH
- Cannot build API path with an empty path segment.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/1a78f007e2082997.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/native-runner-file-handoff.ts:1208
.from(issueComments)
.where(
and(
eq(issueComments.id, existing.commentId),
eq(issueComments.companyId, input.binding.companyId),
eq(issueComments.issueId, input.binding.issueId),
eq(issueComments.authorType, "agent"),
eq(issueComments.authorAgentId, input.binding.agentId),
eq(issueComments.createdByRunId, input.binding.runId),
eq(
issueComments.body,
`Prepared ${verified.title} for this response.`,
),
isNull(issueComments.deletedAt),
),
)
.limit(1);
if (!comment) {
throw new Error("paperclip_runner_file_handoff_existing_binding_invalid");
}
return {
result: {
commandId: `deliverable-prepared:${existing.attachmentId}`,
disposition: "duplicate",
attachmentId: existing.attachmentId,
contentPath: `/api/attachments/${existing.attachmentId}/content`,
downloadPath: `/api/attachments/${existing.attachmentId}/content?download=1`,
stateRevision: statusVersion,
entityRefs: [
existing.attachmentId,
...(existing.workProductId ? [existing.workProductId] : []),
comment.id,
],
scheduledWakeIds: [],
},
rollbackDefinitePreCommitFailure: null,
};View on GitHub (pinned to 3f1d897a7c)