{"record":{"id":"1a7c29ef83055e62","repo":"paperclipai/paperclip","slug":"github-attachment-canonical-mapping-ambiguous","errorCode":"github_attachment_canonical_mapping_ambiguous","errorMessage":"github_attachment_canonical_mapping_ambiguous","messagePattern":"github_attachment_canonical_mapping_ambiguous","errorType":"error_code","errorClass":"GitHubAttachmentUnavailableError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-github-attachments.ts","lineNumber":655,"sourceCode":"    // The second form was observed in the exact App-rendered live comment.\n    // Both the original-anchor and signed-anchor forms enter one candidate set\n    // so duplicated or mixed renderings cannot silently choose a target.\n    if (\n      !target ||\n      (href !== locator.url && (href !== src || !signedImage(href)))\n    )\n      throw new GitHubAttachmentUnavailableError(\n        \"github_attachment_canonical_target_denied\",\n      );\n    candidates.push(target);\n  }\n  const sameAssetImages = [...fragment.querySelectorAll(\"img[src]\")].filter(\n    (image) =>\n      image.getAttribute(\"src\") === locator.url ||\n      sameAssetImage(image.getAttribute(\"src\")!),\n  );\n  if (candidates.length > 1 || sameAssetImages.length > 1)\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_mapping_ambiguous\",\n    );\n  if (candidates.length === 1 && sameAssetImages.length === 1)\n    return candidates[0]!;\n  if (\n    [...fragment.querySelectorAll(\"img[src]\")].some((image) =>\n      signedImage(image.getAttribute(\"src\")!),\n    )\n  )\n    throw new GitHubAttachmentUnavailableError(\n      \"github_attachment_canonical_image_without_source_anchor\",\n    );\n  throw new GitHubAttachmentUnavailableError(\n    \"github_attachment_canonical_anchor_missing\",\n  );\n}\n\nfunction imageSignatureMatches(body: Buffer, mime: string): boolean {","sourceCodeStart":637,"sourceCodeEnd":673,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-attachments.ts#L637-L673","documentation":"GitHubAttachmentUnavailableError with code github_attachment_canonical_mapping_ambiguous is thrown when the rendered HTML yields more than one candidate anchor target or more than one img referencing the same asset. A unique 1:1 mapping between the source anchor and the signed image is required; any duplication means the resolver cannot silently pick a target.","triggerScenarios":"The comment body references the same asset URL twice (duplicate markdown image, image repeated inside two anchors), so candidates.length > 1 or sameAssetImages.length > 1 after HTML parsing.","commonSituations":"Users pasting the same image twice in one comment; quote-reply blocks that re-embed the original image; comment edits that left a duplicated image behind; diffs/changelogs reusing the same screenshot URL.","solutions":["Edit the GitHub comment to reference the asset image exactly once, then re-resolve (body must still match the recorded SHA-256, so re-record the locator hash after editing).","Remove quoted/duplicated blocks containing the same image from the comment.","If your system stores the locator, update sourceBodySha256 after any intentional comment edit before resolving."],"exampleFix":"// before (same image twice)\n![shot](url-assets/abc) ... > ![shot](url-assets/abc)\n// after\n![shot](url-assets/abc)","handlingStrategy":"validation","validationCode":"function assetMentionedOnce(body: string, assetUrl: string): boolean {\n  const id = assetUrl.split('/').pop();\n  return (body.match(new RegExp(id!, 'g')) || []).length === 1;\n}\nif (!assetMentionedOnce(commentBody, attachment.url)) console.warn('duplicate asset reference; resolution will be ambiguous');","typeGuard":null,"tryCatchPattern":"try {\n  const attachment = await prepareGitHubPublicAttachment(att, signal, resolver);\n} catch (e) {\n  if (e instanceof GitHubAttachmentUnavailableError && e.code === 'github_attachment_canonical_mapping_ambiguous') {\n    await requireCommentCleanup(issue); // ask author to deduplicate the image\n  } else throw e;\n}","preventionTips":["Reference each uploaded asset exactly once per comment.","Avoid quote-replies that re-embed the same image.","After comment edits, re-record the locator's body SHA-256.","Strip duplicated images from changelog/quote blocks before upload."],"tags":["github","attachments","ambiguity","images"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}