{"record":{"id":"7903399422129b24","repo":"prometheus/prometheus","slug":"body-error-res-statustext","errorCode":null,"errorMessage":"body.error || res.statusText","messagePattern":"body\\.error \\|\\| res\\.statusText","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/ui/mantine-ui/src/pages/TSDBStatusPage.tsx","lineNumber":120,"sourceCode":"    }\n    if (endTime !== null) {\n      params.append(\"end\", (endTime / 1000).toString());\n    }\n\n    setDeleting(true);\n    try {\n      const res = await fetch(\n        `${pathPrefix}/${API_PATH}/admin/tsdb/delete_series?${params.toString()}`,\n        {\n          method: \"POST\",\n          credentials: \"same-origin\",\n        }\n      );\n\n      if (!res.ok) {\n        if (res.headers.get(\"content-type\")?.startsWith(\"application/json\")) {\n          const body = await res.json();\n          throw new Error(body.error || res.statusText);\n        }\n        throw new Error(res.statusText);\n      }\n\n      setDeleteSuccess(\n        `Successfully deleted series matching: ${matchList.join(\", \")}`\n      );\n      setMatchers(\"\");\n      setStartTime(null);\n      setEndTime(null);\n    } catch (err) {\n      setDeleteError(err instanceof Error ? err.message : \"Unknown error\");\n    } finally {\n      setDeleting(false);\n    }\n  };\n\n  const handleCleanTombstones = async () => {","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/prometheus/prometheus/blob/44d6a0e0b1dbdcba4e68853d50e0bafc87d90507/web/ui/mantine-ui/src/pages/TSDBStatusPage.tsx#L102-L138","documentation":"In the mantine-ui TSDB status page, POSTing to /api/v1/admin/tsdb/delete_series failed with a JSON body; the error message is the server's body.error if present, else the statusText. delete_series is the admin API for dropping series and fails on invalid matchers, parse errors, or when admin APIs are disabled.","triggerScenarios":"Invalid matcher syntax in the match field, start/end outside retained data bounds, or the admin API disabled — the server responds 400/501/503 with {\"status\":\"error\",\"error\":\"...\"}.","commonSituations":"--web.enable-admin-api not passed to Prometheus (admin APIs off by default); malformed matchers like '{foo' or empty match; timestamps outside the retention window.","solutions":["Start Prometheus with --web.enable-admin-api to enable delete_series.","Fix matcher syntax (must be a valid PromQL selector, e.g. {job=\"x\"}).","Ensure start/end times fall within existing data and are not equal/empty in invalid ways.","Read body.error in the shown message for the exact server reason."],"exampleFix":"# before\nprometheus --config.file=prometheus.yml\n\n# after (enable the admin API this page needs)\nprometheus --config.file=prometheus.yml --web.enable-admin-api","handlingStrategy":"validation","validationCode":"function validMatchers(m: string): boolean {\n  try {\n    // reuse prometheus regexp/label syntax check, minimally: non-empty, balanced braces\n    return m.trim().length > 0 && (m.match(/{/g) ?? []).length === (m.match(/}/g) ?? []).length;\n  } catch {\n    return false;\n  }\n}","typeGuard":"function isJSONErrorBody(body: unknown): body is { error: string } {\n  return typeof (body as any)?.error === 'string';\n}","tryCatchPattern":"catch (err) {\n  if (err instanceof Error) {\n    setDeleteError(err.message); // body.error carries the exact server reason\n    if (err.message.includes('admin APIs disabled')) promptEnableAdminApi();\n  }\n}","preventionTips":["Run Prometheus with --web.enable-admin-api when using the delete UI.","Validate matcher syntax client-side before POSTing delete_series.","Ensure start/end times intersect the retained data window."],"tags":["tsdb","admin-api","delete-series","mantine-ui"],"backgroundTag":null,"analyzedSha":"44d6a0e0b1dbdcba4e68853d50e0bafc87d90507","analyzedAt":"2026-08-15T08:44:33.427Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}