paperclipai/paperclip · warning · GitHubAttachmentUnavailableError
github_attachment_unsafe_redirect
github_attachment_unsafe_redirect
Error message
github_attachment_unsafe_redirect
What it means
prepareGitHubPublicAttachment downloads public GitHub attachment URLs with redirects handled manually (redirect: "manual"). When a response is a redirect (3xx in REDIRECT_STATUSES) and the Location header is missing or fails the SSRF-safe allowedRedirect check, or the redirect budget (4 hops) is exhausted, the service throws GitHubAttachmentUnavailableError with code "github_attachment_unsafe_redirect". This is thrown at chat-github-attachments.ts:736, inside the redirect loop.
Source
Thrown at server/src/services/chat-github-attachments.ts:736
},
{
allowPrivateNetwork: false,
connectTimeoutMs: 5000,
responseTimeoutMs: DOWNLOAD_TIMEOUT_MS,
error: () =>
new GitHubAttachmentUnavailableError(
"github_attachment_download_failed",
),
},
);
if (REDIRECT_STATUSES.has(response.status)) {
const target = allowedRedirect(
response.headers.get("location") ?? "",
url.href,
);
await response.body?.cancel();
if (!target || redirects === 3)
throw new GitHubAttachmentUnavailableError(
"github_attachment_unsafe_redirect",
);
url = target;
continue;
}
const rejectResponse = async (
code: GitHubAttachmentUnavailableError["code"],
): Promise<never> => {
await response.body?.cancel();
throw new GitHubAttachmentUnavailableError(code);
};
if (
response.status === 401 ||
response.status === 403 ||
response.status === 404
) {
const commentRequest = githubAttachmentCommentRequest(attachment);
if (!resolvedCanonicalComment && resolveComment && commentRequest) {View on GitHub (pinned to 01ad858492)
Solutions
- Confirm the attachment URL is a current, public GitHub attachment URL that resolves directly or within 3 redirects
- Check that the redirect target host is publicly routable (no private IPs / localhost / internal domains) and permitted by allowedRedirect
- Regenerate or re-upload the attachment so it uses a fresh non-redirecting URL
- If behind a proxy, ensure it does not rewrite GitHub attachment URLs to other hosts
Example fix
// before: attachment URL redirects beyond allowed hosts/depth
const attachment = { url: "https://github.com/user-attachments/assets/expired-id" };
// after: use a resolvable public URL or re-fetch via the GitHub API asset URL
const attachment = { url: await getFreshPublicAttachmentUrl(repo, attachmentId) }; Defensive patterns
Strategy: try-catch
Validate before calling
const u = new URL(attachmentUrl); if (u.protocol !== "https:" || !/(^|\.)github\.com$|(^|\.)githubusercontent\.com$/.test(u.hostname)) reject(attachmentUrl);
Type guard
function isPublicHttpsUrl(u) { try { const p = new URL(u); return (p.protocol === "https:" && !/^\d+\.\d+\.\d+\.\d+$/.test(p.hostname) && p.hostname !== "localhost"); } catch { return false; } } Try / catch
try { return await prepareGitHubPublicAttachment(attachment, signal); } catch (e) { if (e instanceof GitHubAttachmentUnavailableError && e.code === "github_attachment_unsafe_redirect") { markAttachmentUnavailable(attachment, e.code); return null; } throw e; } Prevention
- Prefer fresh, direct GitHub user-attachment URLs over long-lived links likely to redirect
- Keep attachment URLs on official GitHub/githubusercontent hosts
- Don't proxy GitHub media through internal hosts that inject redirects
- Test redirect handling with stubs that terminate within 3 hops
When it happens
Trigger: A 3xx response whose Location header is empty; a Location that points to a private-network or otherwise disallowed host (allowedRedirect returns null); or a chain requiring a 5th redirect (redirects === 3).
Common situations: GitHub media URLs (github.com/user-attachments/...) re-pointing to a host not on the allowed redirect list; expired media links redirecting to a login/error page on a different domain; misconfigured proxies injecting redirects to internal hosts; deeply chained CDN redirects exceeding 3 hops.
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
- github_attachment_canonical_html_unavailable
- github_attachment_canonical_file_unsupported
- github_attachment_canonical_image_count_invalid
- github_attachment_canonical_target_denied
- github_attachment_canonical_mapping_ambiguous
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/1c2debed9393ebb6.
Report an issue: GitHub.