paperclipai/paperclip · error · GitHubAttachmentUnavailableError

github_attachment_source_mismatch

github_attachment_source_mismatch

Error message

github_attachment_source_mismatch

What it means

githubAttachmentCommentFetch returns a hardened fetch wrapper that only permits the single canonical GitHub API comment request derived from the attachment locator. If the wrapped fetch is invoked with anything other than the expected URL string, a non-GET method, or after the expected request record fails its own shape validation, it throws GitHubAttachmentUnavailableError with code github_attachment_source_mismatch. This is a deliberate SSRF/provenance guard: the authenticated client must never be pointed at a caller-chosen origin.

Source

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

      : "application/vnd.github.full+json",
  };
}

const MAX_COMMENT_RESPONSE_BYTES = 1_048_576;

/** Octokit's authenticated request may go only to this one fixed API route. */
export function githubAttachmentCommentFetch(
  expected: GitHubAttachmentCommentRequest,
  signal: AbortSignal,
): typeof fetch {
  return async (input, init) => {
    if (
      !isGitHubAttachmentCommentRequest(expected) ||
      typeof input !== "string" ||
      input !== expected.url ||
      init?.method !== "GET"
    )
      throw new GitHubAttachmentUnavailableError(
        "github_attachment_source_mismatch",
      );
    signal.throwIfAborted();
    const headers = new Headers(init.headers);
    if (headers.get("accept") !== expected.accept || headers.has("cookie"))
      throw new GitHubAttachmentUnavailableError(
        "github_attachment_source_mismatch",
      );
    const response = await guardedRemoteHttpFetch(
      expected.url,
      {
        ...init,
        method: "GET",
        headers,
        credentials: "omit",
        redirect: "manual",
        signal,
      },

View on GitHub (pinned to 01ad858492)

Solutions

  1. Call the wrapper only with expected.url verbatim and method "GET" — no URL rewriting, no added query parameters
  2. Regenerate the expected request via githubAttachmentCommentRequest(attachment) so the locator has version 2 and sourceBodySha256 set
  3. Ensure redirects are handled by the wrapper itself (it uses redirect: "manual") and no upstream code follows them through this fetch
  4. If you need a different endpoint, use a normal fetch — this wrapper is intentionally restricted to the one canonical comment route

Example fix

// before: caller rewrote the URL with query params
await scopedFetch(`${expected.url}?per_page=1`, { method: "GET", headers });
// after: use the expected URL exactly
await scopedFetch(expected.url, { method: "GET", headers });
Defensive patterns

Strategy: type-guard

Validate before calling

const expected = githubAttachmentCommentRequest(attachment);
if (!expected) throw new Error("attachment locator is not a v2 github comment attachment");
// pass expected.url verbatim to the wrapped fetch, method GET only

Type guard

function isCanonicalCommentFetchCall(expected: GitHubAttachmentCommentRequest, input: RequestInfo | URL, init?: RequestInit): boolean {
  return typeof input === "string" && input === expected.url && init?.method === "GET";
}

Try / catch

try {
  const res = await scopedFetch(expected.url, { method: "GET", headers: { accept: expected.accept }, signal });
} catch (err) {
  if (err instanceof GitHubAttachmentUnavailableError && err.code === "github_attachment_source_mismatch") {
    throw new Error("request was rewritten; only the exact canonical comment URL with GET is allowed");
  } else throw err;
}

Prevention

When it happens

Trigger: The returned fetch is called with input !== expected.url, a non-string input, init.method !== "GET", or isGitHubAttachmentCommentRequest(expected) is false (locator missing version 2 or sourceBodySha256).

Common situations: Octokit or an internal HTTP client rewrites the URL (adds query params, different host like github.com instead of api.github.com) before calling fetch; a redirect or retry logic issues a non-GET method; code reuses this wrapper for a different attachment whose locator lacks the required fields; proxy middleware modifies the request URL.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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