From b10b78cb75933886e457d3f071fec9fd86420afb Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Wed, 11 Mar 2026 18:26:02 -0600 Subject: [PATCH] fix: guard formatCost against non-number cost values (#74) Handle both plain number and { total: number } shapes for msg.usage.cost in snapshotUnitMetrics, and coerce formatCost input to prevent crashes when cost is null/undefined/NaN from corrupted ledger data. Co-Authored-By: Claude Opus 4.6 --- src/resources/extensions/gsd/metrics.ts | 12 +++++++----- src/resources/extensions/subagent/index.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/resources/extensions/gsd/metrics.ts b/src/resources/extensions/gsd/metrics.ts index 63ed65662..a9e875f61 100644 --- a/src/resources/extensions/gsd/metrics.ts +++ b/src/resources/extensions/gsd/metrics.ts @@ -129,8 +129,9 @@ export function snapshotUnitMetrics( tokens.cacheRead += msg.usage.cacheRead ?? 0; tokens.cacheWrite += msg.usage.cacheWrite ?? 0; tokens.total += msg.usage.totalTokens ?? 0; - if (msg.usage.cost) { - cost += msg.usage.cost.total ?? 0; + if (msg.usage.cost != null) { + const c = msg.usage.cost; + cost += typeof c === "number" ? c : (c.total ?? 0); } } // Count tool calls in this message @@ -296,9 +297,10 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals { // ─── Formatting helpers ─────────────────────────────────────────────────────── export function formatCost(cost: number): string { - if (cost < 0.01) return `$${cost.toFixed(4)}`; - if (cost < 1) return `$${cost.toFixed(3)}`; - return `$${cost.toFixed(2)}`; + const n = Number(cost) || 0; + if (n < 0.01) return `$${n.toFixed(4)}`; + if (n < 1) return `$${n.toFixed(3)}`; + return `$${n.toFixed(2)}`; } /** diff --git a/src/resources/extensions/subagent/index.ts b/src/resources/extensions/subagent/index.ts index 36196f0b8..2d31ca545 100644 --- a/src/resources/extensions/subagent/index.ts +++ b/src/resources/extensions/subagent/index.ts @@ -53,7 +53,7 @@ function formatUsageStats( if (usage.output) parts.push(`↓${formatTokens(usage.output)}`); if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`); if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`); - if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`); + if (usage.cost) parts.push(`$${(Number(usage.cost) || 0).toFixed(4)}`); if (usage.contextTokens && usage.contextTokens > 0) { parts.push(`ctx:${formatTokens(usage.contextTokens)}`); }