paperclipai/paperclip · error · GitHubAttachmentUnavailableError

github_attachment_download_failed

github_attachment_download_failed

Error message

github_attachment_download_failed

What it means

The outer catch of prepareGitHubPublicAttachment (chat-github-attachments.ts:850-855) rethrows GitHubAttachmentUnavailableError as-is, but wraps any other thrown value — network failures, DNS errors, timeouts, SSRF-guard rejections from guardedRemoteHttpFetch, AbortSignal timeouts, URL parse errors — into a generic GitHubAttachmentUnavailableError with code "github_attachment_download_failed". It is the umbrella error meaning the download could not be completed for a reason not covered by a more specific code.

Source

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

        type: mimeType.startsWith("image/")
          ? "image"
          : mimeType.startsWith("audio/")
            ? "audio"
            : mimeType.startsWith("video/")
              ? "video"
              : "file",
        name,
        mimeType,
        size,
        fetchData: async () => body,
      };
    }
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_unsafe_redirect",
    );
  } catch (error) {
    if (error instanceof GitHubAttachmentUnavailableError) throw error;
    throw new GitHubAttachmentUnavailableError(
      "github_attachment_download_failed",
    );
  }
}

View on GitHub (pinned to 01ad858492)

Solutions

  1. Check server egress: curl the attachment URL from the server host to test DNS/firewall reachability
  2. Verify DOWNLOAD_TIMEOUT_MS is adequate for large attachments on slow links
  3. Log/inspect the underlying cause — the original error is swallowed, so reproduce with the same URL outside the wrapper
  4. Confirm the attachment locator URL is a valid absolute http(s) URL

Example fix

// before: opaque failure with no visibility
} catch (error) {
  throw new GitHubAttachmentUnavailableError("github_attachment_download_failed");
}
// after: preserve the cause for diagnosis
} catch (error) {
  if (error instanceof GitHubAttachmentUnavailableError) throw error;
  logger.warn("github attachment download failed", { cause: String(error) });
  throw new GitHubAttachmentUnavailableError("github_attachment_download_failed");
}
Defensive patterns

Strategy: retry

Validate before calling

const u = new URL(attachment.url); if (u.protocol !== "https:") throw new Error("attachment locator must be https"); // check egress once at boot: await fetch("https://github.com/robots.txt")

Type guard

function hasValidLocator(a) { if (!a?.url) return false; try { const u = new URL(a.url); return u.protocol === "https:"; } catch { return false; } }

Try / catch

try { return await prepareGitHubPublicAttachment(attachment, signal, resolveComment); } catch (e) { if (e?.code === "github_attachment_download_failed") { if (isRetryable(signal)) return retry(prepareGitHubPublicAttachment, attachment); return downloadFailedCard(attachment); } throw e; }

Prevention

When it happens

Trigger: guardedRemoteHttpFetch rejects (connect timeout 5000ms, private-network target blocked, DNS failure); AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) fires; new URL(locator.url) throws on a malformed stored URL; any non-GitHubAttachmentUnavailableError exception inside the try block.

Common situations: No outbound internet access / DNS misconfiguration on the server host; corporate firewall blocking objects.githubusercontent.com; long downloads exceeding DOWNLOAD_TIMEOUT_MS; attachment handles whose stored locator URL got corrupted; IPv6 vs IPv4 egress issues.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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