paperclipai/paperclip · error · GitHubAttachmentUnavailableError

github_attachment_canonical_anchor_missing

github_attachment_canonical_anchor_missing

Error message

github_attachment_canonical_anchor_missing

What it means

GitHubAttachmentUnavailableError with code github_attachment_canonical_anchor_missing is the terminal fallback in resolveCanonicalAttachmentTargetOrThrow: after checking all anchors and images, the rendered HTML contains no anchor or img matching the locator's asset at all. The source attachment URL simply does not appear in the canonical rendering, so no signed target can be derived.

Source

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

    (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 {
  if (mime === "image/png")
    return body
      .subarray(0, 8)
      .equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
  if (mime === "image/jpeg" || mime === "image/jpg")
    return body[0] === 255 && body[1] === 216 && body[2] === 255;
  if (mime === "image/gif")
    return /^(GIF87a|GIF89a)$/.test(body.subarray(0, 6).toString("ascii"));
  if (mime === "image/webp")
    return (
      body.subarray(0, 4).toString("ascii") === "RIFF" &&
      body.subarray(8, 12).toString("ascii") === "WEBP"
    );

View on GitHub (pinned to 01ad858492)

Solutions

  1. Verify the locator points at the comment that actually contains the image; re-derive the locator from the live thread.
  2. Re-upload or re-add the image to the comment if it was removed, then re-record the body SHA-256.
  3. Re-fetch the comment fresh — an edit race could have served an outdated body (which would otherwise fail earlier as body mismatch).
Defensive patterns

Strategy: fallback

Validate before calling

function bodyMentionsAsset(body: string, assetUrl: string): boolean {
  return body.includes(assetUrl.split('/').pop()!);
}
if (!bodyMentionsAsset(currentCommentBody, attachment.url)) console.warn('asset absent from this comment');

Try / catch

try {
  await prepareGitHubPublicAttachment(attachment, signal, resolver);
} catch (e) {
  if (e instanceof GitHubAttachmentUnavailableError) {
    // resolveCanonical returns null for unavailable; surface a user-facing 'attachment no longer present' state
    markAttachmentMissing(attachment);
  } else throw e;
}

Prevention

When it happens

Trigger: The comment body no longer contains the attachment (image deleted/edited out), the asset was uploaded in a different comment than the one fetched, or the locator URL never appeared in this comment's rendered HTML.

Common situations: Stale locator after a comment edit removed the image; resolving against the wrong comment in the thread; GitHub render omitting the image (e.g. content suppressed); attachment moved to another issue/comment.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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