paperclipai/paperclip · error · GitHubAttachmentUnavailableError

github_attachment_canonical_source_mismatch

github_attachment_canonical_source_mismatch

Error message

github_attachment_canonical_source_mismatch

What it means

After parsing the canonical comment, resolveCanonicalAttachmentTargetOrThrow cross-checks that the API row is the exact message the attachment locator was pinned to: row.id must equal locator.sourceMessageId, row.url must be a string matching the canonical request URL case-insensitively, and for review-comment threads the pull_request_url and in_reply_to_id must match. Any mismatch throws github_attachment_canonical_source_mismatch because the fetched rendering would be evidence for a different source than the attachment claims.

Source

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

    !request ||
    !value ||
    typeof value !== "object" ||
    Array.isArray(value)
  )
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_canonical_response_unavailable",
    );
  const row = value as Record<string, unknown>;
  const thread =
    /^github:([^:]+):(?:(issue):)?([1-9][0-9]*)(?::rc:([1-9][0-9]*))?$/i.exec(
      locator.sourceThreadId,
    )!;
  if (
    String(row.id) !== locator.sourceMessageId ||
    typeof row.url !== "string" ||
    row.url.toLowerCase() !== request.url.toLowerCase()
  )
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_canonical_source_mismatch",
    );
  if (
    typeof row.body !== "string" ||
    row.body.length > 200_000 ||
    createHash("sha256").update(row.body).digest("hex") !==
      locator.sourceBodySha256
  )
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_canonical_body_mismatch",
    );
  if (typeof row.body_html !== "string" || row.body_html.length > 600_000)
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_canonical_html_unavailable",
    );
  if (thread[4]) {
    if (
      row.pull_request_url !==

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-attach the file from the current, live comment so the stored locator (sourceMessageId, URL, body hash) is regenerated against the actual source.
  2. Verify the attachment locator was created from the same repo/issue/comment you are fetching; fix stale or copied descriptors.
  3. For review comments, confirm you fetched the /pulls/{n}/comments/{id} resource (whose issue_url/pull_request_url fields match) rather than a plain issue comment.
  4. Clear caches of previously rendered attachment targets and re-run resolution after the comment's URL or thread changed.

Example fix

// before: resolving an attachment against a row fetched from a different comment id
const res = await fetch(`${base}/issues/comments/${wrongId}`);
// after: fetch the exact comment id pinned in the locator
const res = await fetch(`${base}/issues/comments/${locator.sourceMessageId}`);
Defensive patterns

Strategy: validation

Validate before calling

// Validate identity fields before handing the row to the resolver
const ok =
  String(row.id) === locator.sourceMessageId &&
  typeof row.url === 'string' &&
  row.url.toLowerCase() === canonicalUrl.toLowerCase();
if (!ok) throw new Error('fetched comment does not match the attachment source');

Type guard

function matchesSource(row: Record<string, unknown>, locator: AttachmentLocator, requestUrl: string): boolean {
  return String(row.id) === locator.sourceMessageId &&
    typeof row.url === 'string' &&
    row.url.toLowerCase() === requestUrl.toLowerCase();
}

Try / catch

const target = resolveGitHubCommentAttachmentTarget(attachment, value);
if (target === null) {
  // source mismatch (or any other validation failure) mapped to null:
  // re-capture the locator from the live comment instead of retrying blindly
  await recaptureAttachmentLocator(attachment);
  return null;
}

Prevention

When it happens

Trigger: The API response's `id` differs from the stored sourceMessageId; `row.url` is missing, non-string, or differs (case-insensitively) from the canonical api.github.com comment URL; for review comments (locator with :rc:), row.pull_request_url is not the expected /pulls/{n} URL or in_reply_to_id (falling back to id) does not match the review-comment id; for issue comments row.issue_url does not match the expected /issues/{n} URL.

Common situations: The comment was edited/moved so its URL changed; the attachment descriptor was copied from another issue or thread (stale locator); GitHub redirects a review-comment lookup to a different endpoint; resolving an attachment against the wrong repository's comment with the same numeric id.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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