paperclipai/paperclip · error · GitHubAttachmentUnavailableError

github_attachment_canonical_mapping_ambiguous

github_attachment_canonical_mapping_ambiguous

Error message

github_attachment_canonical_mapping_ambiguous

What it means

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.

Source

Thrown at server/src/services/chat-github-attachments.ts:655

    // The second form was observed in the exact App-rendered live comment.
    // Both the original-anchor and signed-anchor forms enter one candidate set
    // so duplicated or mixed renderings cannot silently choose a target.
    if (
      !target ||
      (href !== locator.url && (href !== src || !signedImage(href)))
    )
      throw new GitHubAttachmentUnavailableError(
        "github_attachment_canonical_target_denied",
      );
    candidates.push(target);
  }
  const sameAssetImages = [...fragment.querySelectorAll("img[src]")].filter(
    (image) =>
      image.getAttribute("src") === locator.url ||
      sameAssetImage(image.getAttribute("src")!),
  );
  if (candidates.length > 1 || sameAssetImages.length > 1)
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_canonical_mapping_ambiguous",
    );
  if (candidates.length === 1 && sameAssetImages.length === 1)
    return candidates[0]!;
  if (
    [...fragment.querySelectorAll("img[src]")].some((image) =>
      signedImage(image.getAttribute("src")!),
    )
  )
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_canonical_image_without_source_anchor",
    );
  throw new GitHubAttachmentUnavailableError(
    "github_attachment_canonical_anchor_missing",
  );
}

function imageSignatureMatches(body: Buffer, mime: string): boolean {

View on GitHub (pinned to 01ad858492)

Solutions

  1. 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).
  2. Remove quoted/duplicated blocks containing the same image from the comment.
  3. If your system stores the locator, update sourceBodySha256 after any intentional comment edit before resolving.

Example fix

// before (same image twice)
![shot](url-assets/abc) ... > ![shot](url-assets/abc)
// after
![shot](url-assets/abc)
Defensive patterns

Strategy: validation

Validate before calling

function assetMentionedOnce(body: string, assetUrl: string): boolean {
  const id = assetUrl.split('/').pop();
  return (body.match(new RegExp(id!, 'g')) || []).length === 1;
}
if (!assetMentionedOnce(commentBody, attachment.url)) console.warn('duplicate asset reference; resolution will be ambiguous');

Try / catch

try {
  const attachment = await prepareGitHubPublicAttachment(att, signal, resolver);
} catch (e) {
  if (e instanceof GitHubAttachmentUnavailableError && e.code === 'github_attachment_canonical_mapping_ambiguous') {
    await requireCommentCleanup(issue); // ask author to deduplicate the image
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/1a7c29ef83055e62. Report an issue: GitHub.