{"record":{"id":"6bba888cc3e1ace6","repo":"koala73/worldmonitor","slug":"umami-volume-is-not-ready-volume-status-unknown","errorCode":null,"errorMessage":"Umami volume is not ready: ${volume.status ?? 'unknown'}","messagePattern":"Umami volume is not ready: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/check-umami-storage.mjs","lineNumber":119,"sourceCode":"  const identity = volumeIdentity(volume);\n  if (identity === null) throw new Error('Umami volume must have a stable identity');\n\n  const priorCapacityMB = finiteNonNegative(previousState?.capacityMB);\n  const sameVolume = previousState?.volumeIdentity === identity && priorCapacityMB === capacityMB;\n  const samples = (sameVolume ? normalizeSamples(previousState?.samples, nowMs) : [])\n    .filter((sample) => Date.parse(sample.sampledAt) !== nowMs);\n  samples.push({ sampledAt: new Date(nowMs).toISOString(), currentSizeMB });\n  return { version: 1, volumeIdentity: identity, capacityMB, samples };\n}\n\nexport function evaluateUmamiStorage({ volume, samples = [], now = Date.now() }) {\n  const nowMs = timestampMs(now);\n  const capacityMB = finiteNonNegative(volume?.sizeMB);\n  const currentSizeMB = finiteNonNegative(volume?.currentSizeMB);\n  if (nowMs === null) throw new Error('Storage evaluation time must be a valid timestamp');\n  if (capacityMB === null || capacityMB <= 0) throw new Error('Umami volume sizeMB must be greater than zero');\n  if (currentSizeMB === null) throw new Error('Umami volume currentSizeMB must be a non-negative number');\n  if (volume.status !== 'Ready') throw new Error(`Umami volume is not ready: ${volume.status ?? 'unknown'}`);\n\n  const usageRatio = currentSizeMB / capacityMB;\n  const points = normalizeSamples(samples, nowMs)\n    .filter((sample) => Date.parse(sample.sampledAt) !== nowMs)\n    .map((sample) => ({ day: (Date.parse(sample.sampledAt) - nowMs) / DAY_MS, sizeMB: sample.currentSizeMB }));\n  points.push({ day: 0, sizeMB: currentSizeMB });\n\n  let growthMBPerDay = null;\n  let projectedHeadroomDays = null;\n  if (-points[0].day >= UMAMI_STORAGE_POLICY.minimumTrendSpanDays) {\n    const meanDay = points.reduce((sum, point) => sum + point.day, 0) / points.length;\n    const meanSizeMB = points.reduce((sum, point) => sum + point.sizeMB, 0) / points.length;\n    let covariance = 0;\n    let variance = 0;\n    for (const point of points) {\n      covariance += (point.day - meanDay) * (point.sizeMB - meanSizeMB);\n      variance += (point.day - meanDay) ** 2;\n    }","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/check-umami-storage.mjs#L101-L137","documentation":"evaluateUmamiStorage computes usage trends and projections for an Umami volume, but only makes sense when the volume is in the 'Ready' state. It throws when volume.status is anything else (or undefined, rendered as 'unknown'), because size/current-size samples for a non-Ready volume would produce misleading projections. The check runs after validating the timestamp, sizeMB, and currentSizeMB inputs.","triggerScenarios":"Passing a volume object whose status is e.g. 'Pending', 'Attaching', 'Failed', or missing entirely, while still supplying valid sizeMB and currentSizeMB values.","commonSituations":"Running the storage check immediately after resizing/creating a volume before it finished provisioning, checking a volume stuck in a failed attach state, or scraping status from an API that omits the field.","solutions":["Wait for the volume to reach status 'Ready' (poll or re-run the check later) before evaluating storage.","If the volume is stuck non-Ready, investigate the provider volume state (attach errors, provisioning failures) and repair it.","If status is missing from your data source, fix the scrape/mapping so volume.status is populated, then re-run."],"exampleFix":"// before\nevaluateUmamiStorage({ volume: { sizeMB: 1024, currentSizeMB: 512 }, samples, now });\n// after\nif (volume.status === 'Ready') {\n  evaluateUmamiStorage({ volume, samples, now });\n}","handlingStrategy":"validation","validationCode":"if (volume?.status !== 'Ready') {\n  throw new Error(`defer storage evaluation; volume status=${volume?.status ?? 'unknown'}`);\n}\nevaluateUmamiStorage({ volume, samples, now });","typeGuard":"function isReadyVolume(volume) {\n  return typeof volume === 'object' && volume !== null && volume.status === 'Ready'\n    && typeof volume.sizeMB === 'number' && volume.sizeMB > 0\n    && typeof volume.currentSizeMB === 'number' && volume.currentSizeMB >= 0;\n}","tryCatchPattern":"try {\n  const result = evaluateUmamiStorage({ volume, samples, now });\n} catch (e) {\n  if (e.message.startsWith('Umami volume is not ready')) scheduleRecheck(volume);\n  else throw e;\n}","preventionTips":["Poll volume status until 'Ready' before running storage projections after create/resize.","Alert on volumes stuck in non-Ready states for longer than a threshold.","Ensure the status scrape pipeline never omits the status field."],"tags":["umami","volume","precondition"],"backgroundTag":"invalid-state-transition","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}