{"record":{"id":"9660984fda7b2aab","repo":"amir20/dozzle","slug":"response-statustext","errorCode":null,"errorMessage":"response.statusText","messagePattern":"response\\.statusText","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"assets/components/ContainerViewer/ContainerActionsToolbar.vue","lineNumber":367,"sourceCode":"    selectedLevels.forEach((level) => params.append(\"levels\", level));\n  }\n\n  const url = withBase(`/api/hosts/${container.host}/containers/${container.id}/logs?${params.toString()}`);\n\n  const toastId = \"copy-logs\";\n  showToast(\n    {\n      id: toastId,\n      title: t(\"toolbar.copying-logs\"),\n      message: \"\",\n      type: \"info\",\n    },\n    { once: true },\n  );\n\n  const blobPromise = fetch(url, { headers: { Accept: \"text/plain\" } })\n    .then((response) => {\n      if (!response.ok) throw new Error(response.statusText);\n      return response.blob();\n    })\n    .then((blob) => {\n      removeToast(toastId);\n      showToast(\n        {\n          title: t(\"toasts.copied.title\"),\n          message: t(\"toasts.copied.message\"),\n          type: \"info\",\n        },\n        { expire: 2000 },\n      );\n      return blob;\n    })\n    .catch((err) => {\n      removeToast(toastId);\n      showToast(\n        {","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/assets/components/ContainerViewer/ContainerActionsToolbar.vue#L349-L385","documentation":"ContainerActionsToolbar.vue downloads a text blob (e.g. container logs export) via fetch. If the HTTP response is not ok (status outside 200-299), it throws a plain Error carrying only response.statusText. Because statusText is often empty in HTTP/2 responses, the resulting error message can be blank, making the failure hard to diagnose.","triggerScenarios":"Clicking a download action in the container toolbar while the fetch to `url` (typically /api/hosts/{host}/containers/{id}/logs/download or similar) returns a non-2xx status: container stopped/removed mid-download, auth token expired, reverse proxy returning 502/504, or the endpoint route missing.","commonSituations":"Session expired so the API returns 401; a proxy (nginx/Traefik) intercepts and returns 502; Docker daemon briefly unavailable so backend returns 500; user on HTTP/2 where statusText is the empty string, so the toast shows 'Error' with no detail.","solutions":["Check the network tab for the actual status code and response body of the failed request","Re-authenticate or refresh the page if the status is 401/403","Verify the container still exists and the host is reachable before retrying the download","Improve the error to include response.status since statusText may be empty on HTTP/2"],"exampleFix":"// before\nif (!response.ok) throw new Error(response.statusText);\n// after\nif (!response.ok) throw new Error(`Download failed: ${response.status} ${response.statusText || \"request rejected\"}`);","handlingStrategy":"try-catch","validationCode":"// before downloading\nif (!navigator.onLine) throw new Error(\"offline\");\nconst res = await fetch(url, { method: \"HEAD\", headers: { Accept: \"text/plain\" } });\nif (!res.ok) throw new Error(`Download unavailable: ${res.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  await downloadLogs(url);\n} catch (e) {\n  showToast({ type: \"error\", message: e instanceof Error && e.message ? e.message : t(\"error.something-went-wrong\") });\n}","preventionTips":["Include response.status in thrown messages since statusText is empty on HTTP/2","Check container/host liveness before triggering downloads","Handle 401 by redirecting to login instead of showing a generic toast"],"tags":["network","fetch","http-error","download"],"backgroundTag":"http-error-response","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}