BloopAI/vibe-kanban · error · ApiError
Failed to upload attachment: ${errorText}
Error message
Failed to upload attachment: ${errorText} What it means
attachmentsApi.upload POSTs the file as multipart form data ('image' field) to /api/attachments/upload; on a non-OK response it reads the raw response body as text and throws ApiError('Failed to upload attachment: <server error text>', status, response). The server's error text is embedded directly, so the message content reflects whatever the backend returned (validation error, auth failure, size limit, etc.).
Source
Thrown at packages/web-core/src/shared/lib/api.ts:1138
return handleApiResponse<string>(response);
},
};
// Workspace attachments API
export const attachmentsApi = {
upload: async (attachment: File): Promise<AttachmentResponse> => {
const formData = new FormData();
formData.append('image', attachment);
const response = await makeLocalApiRequest('/api/attachments/upload', {
method: 'POST',
body: formData,
credentials: 'include',
});
if (!response.ok) {
const errorText = await response.text();
throw new ApiError(
`Failed to upload attachment: ${errorText}`,
response.status,
response
);
}
return handleApiResponse<AttachmentResponse>(response);
},
uploadForTask: async (
taskId: string,
attachment: File
): Promise<AttachmentResponse> => {
const formData = new FormData();
formData.append('image', attachment);
const response = await makeLocalApiRequest(
`/api/attachments/task/${taskId}/upload`,View on GitHub (pinned to 4deb7eca8f)
Solutions
- Read the embedded errorText in the ApiError message — it contains the server's exact reason (e.g. size limit, bad MIME type)
- Compress or resize the file if it exceeds the server's upload limit, or convert to an accepted type
- Re-authenticate / log back in if the session cookie expired (401/403 status)
- Confirm the backend is running and reachable at the local API base URL
Example fix
// before
const errorText = await response.text();
throw new ApiError(`Failed to upload attachment: ${errorText}`, response.status, response);
// after
const errorText = await response.text();
let detail = errorText;
try { detail = JSON.parse(errorText).message ?? errorText; } catch {}
throw new ApiError(
`Failed to upload attachment (HTTP ${response.status}): ${detail}`,
response.status,
response
); Defensive patterns
Strategy: validation
Validate before calling
const MAX_UPLOAD_BYTES = 10 * 1024 * 1024; // match server limit
const ACCEPTED_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
if (attachment.size > MAX_UPLOAD_BYTES) return showToast('File too large');
if (!ACCEPTED_TYPES.includes(attachment.type)) return showToast('Unsupported file type'); Type guard
function isUploadError(e: unknown): e is ApiError {
return e instanceof ApiError && e.message.startsWith('Failed to upload attachment');
} Try / catch
try {
await attachmentsApi.upload(file);
} catch (e) {
if (isUploadError(e)) showToast(`Upload rejected (${e.status}): ${e.message}`);
else showToast('Upload failed unexpectedly');
} Prevention
- Check file size and MIME type client-side before uploading
- Keep the user's session alive; re-login on 401 before retrying uploads
- Show the embedded server errorText to the user rather than hiding it
- Confirm the backend is running when uploads fail with proxy-style error text
When it happens
Trigger: Uploading a file via attachmentsApi.upload when POST /api/attachments/upload returns non-OK: file too large per server limit, disallowed MIME type, missing/invalid auth session cookie, backend attachment storage unavailable, or task/workspace route variant unavailable (this is the generic local endpoint).
Common situations: Dragging in a screenshot exceeding the configured max upload size; uploading an unsupported file type (server only accepts images); session cookie expired after backend restart; local backend not running so a proxy/404 error text is returned.
Related errors
- result.message || 'Force push failed'
- result.message || 'Push failed'
- Failed to save MCP servers
- Logout failed with status ${response.status}
- Failed to fetch attachment ${type}: ${response.statusText}
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/1a13f835ae51cb9d.
Report an issue: GitHub.