[PR #12899] fix: guard Intl.NumberFormat against non-finite values in TUI #14426

Closed
opened 2026-02-16 18:19:13 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/12899

State: closed
Merged: No


Problem

The TUI crashes with a fatal error when Intl.NumberFormat.format() receives NaN or Infinity:

opentui: fatal: TypeError: Failed to format a number.
    at Intl.NumberFormat.format
    at header.tsx:72

Bun uses JavaScriptCore, which throws on non-finite values passed to Intl.NumberFormat.format() — unlike V8 which returns "NaN".

Root Cause

Both header.tsx and sidebar.tsx sum assistant message costs and pass the result directly to Intl.NumberFormat.format() without validating the total:

const total = pipe(messages(), sumBy((x) => (x.role === "assistant" ? x.cost : 0)))
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(total)
//                                                                                   ^^^^^
// Crashes if total is NaN (from any message with NaN cost)

If any message has a NaN or corrupted cost value, sumBy() / reduce() propagates NaN to the formatter, which throws.

Fix

Apply the same Number.isFinite() guard already established in the codebase:

Existing Pattern Location
if (!Number.isFinite(value)) return 0 session/index.ts:458-461
Number.isFinite(added) ? added : 0 snapshot/index.ts:243-244
.format(Number.isFinite(total) ? total : 0)

Displaying $0.00 is strictly better than crashing the entire TUI.

Files Changed

  • packages/opencode/src/cli/cmd/tui/routes/session/header.tsx — cost memo
  • packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx — cost memo

Verification

  • TypeScript: all 12 workspace packages pass
  • Tests: 902 pass, 1 skip, 1 pre-existing timeout failure (unrelated)

Fixes #12866

**Original Pull Request:** https://github.com/anomalyco/opencode/pull/12899 **State:** closed **Merged:** No --- ## Problem The TUI crashes with a fatal error when `Intl.NumberFormat.format()` receives `NaN` or `Infinity`: ``` opentui: fatal: TypeError: Failed to format a number. at Intl.NumberFormat.format at header.tsx:72 ``` Bun uses JavaScriptCore, which throws on non-finite values passed to `Intl.NumberFormat.format()` — unlike V8 which returns `"NaN"`. ## Root Cause Both `header.tsx` and `sidebar.tsx` sum assistant message costs and pass the result directly to `Intl.NumberFormat.format()` without validating the total: ```typescript const total = pipe(messages(), sumBy((x) => (x.role === "assistant" ? x.cost : 0))) return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(total) // ^^^^^ // Crashes if total is NaN (from any message with NaN cost) ``` If any message has a `NaN` or corrupted `cost` value, `sumBy()` / `reduce()` propagates `NaN` to the formatter, which throws. ## Fix Apply the same `Number.isFinite()` guard already established in the codebase: | Existing Pattern | Location | |---|---| | `if (!Number.isFinite(value)) return 0` | `session/index.ts:458-461` | | `Number.isFinite(added) ? added : 0` | `snapshot/index.ts:243-244` | ```typescript .format(Number.isFinite(total) ? total : 0) ``` Displaying `$0.00` is strictly better than crashing the entire TUI. ## Files Changed - `packages/opencode/src/cli/cmd/tui/routes/session/header.tsx` — cost memo - `packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx` — cost memo ## Verification - TypeScript: all 12 workspace packages pass - Tests: 902 pass, 1 skip, 1 pre-existing timeout failure (unrelated) Fixes #12866
yindo added the pull-request label 2026-02-16 18:19:13 -05:00
yindo closed this issue 2026-02-16 18:19:13 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#14426