BloopAI/vibe-kanban · error · Error
Failed to fetch attachment ${type}: ${response.statusText}
Error message
Failed to fetch attachment ${type}: ${response.statusText} What it means
fetchAttachmentSasUrl requests a short-lived SAS (shared access signature) URL for an attachment from /v1/attachments/{id}/{type}. If the response is not ok it throws Error(`Failed to fetch attachment ${type}: ${response.statusText}`). Note the message carries only HTTP statusText, not the server's JSON error body, so it can be empty for HTTP/2 responses.
Source
Thrown at packages/web-core/src/shared/lib/remoteApi.ts:358
});
if (!response.ok) {
throw await parseErrorResponse(response, 'Failed to delete attachment');
}
}
export async function fetchAttachmentSasUrl(
attachmentId: string,
type: 'file' | 'thumbnail'
): Promise<string> {
const cacheKey = `${attachmentId}:${type}`;
const cached = sasUrlCache.get(cacheKey);
if (cached && Date.now() < cached.expiresAt) {
return cached.url;
}
const response = await makeRequest(`/v1/attachments/${attachmentId}/${type}`);
if (!response.ok) {
throw new Error(
`Failed to fetch attachment ${type}: ${response.statusText}`
);
}
const data: AttachmentUrlResponse = await response.json();
sasUrlCache.set(cacheKey, {
url: data.url,
expiresAt: Date.now() + SAS_URL_TTL_MS,
});
return data.url;
}
View on GitHub (pinned to 4deb7eca8f)
Solutions
- Check response status in network tab; for 404 treat the attachment as gone and hide/degrade the UI
- For 401/403, re-authenticate — makeRequest already retries once on 401, so a persistent 403 means missing permission
- Verify the SAS/storage backend configuration on the remote server
- Retry with backoff on 5xx, since storage backends may be transiently unavailable
Example fix
// before
const url = await fetchAttachmentSasUrl(id, 'thumbnail');
// after
let url: string;
try {
url = await fetchAttachmentSasUrl(id, 'thumbnail');
} catch (e) {
url = PLACEHOLDER_IMAGE; // degrade gracefully
} Defensive patterns
Strategy: fallback
Validate before calling
if (!attachmentId || !['thumbnail','full'].includes(type)) return null; // skip request entirely
Type guard
function isAttachmentUrlResponse(x: unknown): x is AttachmentUrlResponse { return typeof x === 'object' && x !== null && typeof (x as any).url === 'string' && typeof (x as any).expiresAt === 'string'; } Try / catch
let url: string | null = null;
try {
url = await fetchAttachmentSasUrl(attachmentId, type);
} catch {
url = null; // render placeholder
} Prevention
- Always render a placeholder for attachment images so a failed SAS fetch degrades visually
- Respect the sasUrlCache expiry; don't bypass it with manual URL construction
- Treat 404 as permanent (attachment deleted) and stop retrying
- Retry only on 5xx with backoff
When it happens
Trigger: GET /v1/attachments/:id/:type returns non-ok: attachment id no longer exists, SAS service/storage backend down, 401/403 on the attachment scope, or a network gateway error. `type` is inline in the message (e.g. 'thumbnail' or 'full').
Common situations: Opening an old message whose attachment was purged from blob storage; self-hosted deployments where Azure/S3 SAS issuer is misconfigured; expired credentials yielding 403.
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
- Host returned HTTP ${response.status}
- Auth methods lookup failed (${res.status})
- WebRTC offer failed: ${response.status} ${response.statusTex
- Failed to update ${mutation.name} (or server-provided messag
- Failed to delete ${mutation.name} (or server-provided messag
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/44874d032a76cbe1.
Report an issue: GitHub.