{"record":{"id":"a359791124f085ea","repo":"makeplane/plane","slug":"failed-to-fetch-image-response-statustext","errorCode":null,"errorMessage":"Failed to fetch image: ${response.statusText}","messagePattern":"Failed to fetch image: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/web/helpers/cover-image.helper.ts","lineNumber":214,"sourceCode":"/**\n * Uploads a local static image to S3\n */\nexport const uploadCoverImage = async (\n  imageUrl: string,\n  uploadConfig: {\n    workspaceSlug?: string;\n    entityIdentifier: string;\n    entityType: EFileAssetType;\n    isUserAsset?: boolean;\n  }\n): Promise<string> => {\n  const { workspaceSlug, entityIdentifier, entityType, isUserAsset = false } = uploadConfig;\n\n  // Fetch the local image\n  const response = await fetch(imageUrl);\n\n  if (!response.ok) {\n    throw new Error(`Failed to fetch image: ${response.statusText}`);\n  }\n\n  const blob = await response.blob();\n\n  // Validate it's actually an image\n  if (!blob.type.startsWith(\"image/\")) {\n    throw new Error(\"Invalid file type. Please select an image.\");\n  }\n\n  const fileName = imageUrl.split(\"/\").pop()?.split(\"?\")[0] || \"image.jpg\";\n  const file = new File([blob], fileName, { type: blob.type });\n\n  // Upload based on context\n  if (isUserAsset) {\n    const uploadResult = await fileService.uploadUserAsset(\n      {\n        entity_identifier: entityIdentifier,\n        entity_type: entityType,","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/apps/web/helpers/cover-image.helper.ts#L196-L232","documentation":"Thrown by uploadCoverImage in the web cover-image helper when fetch(imageUrl) returns a non-2xx response. The thrown message embeds response.statusText so the HTTP failure reason is visible. This runs before the blob/MIME validation and before uploadUserAsset/uploadFileAsset, so it only covers the source-image fetch step.","triggerScenarios":"The cover image URL is a local_static path that 404s (asset removed/moved); an Unsplash URL that is rate-limited (503) or blocked by CSP/CORS; an external URL whose host returns 403/404; network failure producing a non-OK response; the URL is correct but the server returned 500.","commonSituations":"Selecting a local static cover whose file was deleted from the bundle; Unsplash quota exceeded; corporate proxy/CORS blocking the image host; mis-typed or stale external URL in a saved page.","solutions":["Read response.statusText in the thrown message — it tells you 404 vs 403 vs 500.","For Unsplash URLs, verify rate limits and that the host is reachable from the browser.","For local_static covers, confirm the asset still exists in the served static path.","Add CORS/proxy allowances for the image host, or proxy the image through your backend.","Validate the URL (reachable HEAD request) before calling uploadCoverImage."],"exampleFix":"// before\nconst response = await fetch(imageUrl);\nif (!response.ok) throw new Error(`Failed to fetch image: ${response.statusText}`);\n// after: include status code and rethrow with cause for better triage\nconst response = await fetch(imageUrl);\nif (!response.ok) {\n  throw new Error(`Failed to fetch image (${response.status}): ${response.statusText}`);\n}","handlingStrategy":"try-catch","validationCode":"// HEAD-check the URL before fetching the body\nconst probe = await fetch(imageUrl, { method: 'HEAD' });\nif (!probe.ok) throw new Error(`Image URL not reachable (${probe.status})`);","typeGuard":null,"tryCatchPattern":"try { await uploadCoverImage(imageUrl, uploadConfig); }\ncatch (e) {\n  if (/Failed to fetch image/.test(e.message)) showCoverImageError(e.message);\n  else throw e;\n}","preventionTips":["HEAD-probe external image URLs before passing them to uploadCoverImage.","Whitelist image hosts in CSP/CORS policy (incl. Unsplash).","Prefer proxied/local covers over remote URLs to avoid fetch failures.","Read response.statusText in the thrown message to triage 404 vs 403 vs 5xx."],"tags":["assets","web","cover-image","fetch","network","unsplash","cors"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}