From ae2d610d2dd4363acc1b373ef2d785b95bc387e6 Mon Sep 17 00:00:00 2001 From: leecoder Date: Fri, 24 Jul 2026 23:07:31 +0900 Subject: [PATCH] fix(opencode): sanitize NaN tokens in getUsage and recover null on read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getUsage() passed totalTokens through without the safe() guard applied to all other token fields. When a provider returns NaN (e.g. undefined + number arithmetic), JSON.stringify serializes it as null. On read-back, null violates Schema.optional(Schema.Finite) causing a 500 MessageDecodeError. Prevention (session.ts): apply Number.isFinite check to totalTokens before storing, matching the safe() pattern used for all other token fields. Recovery (message-v2.ts): sanitize tokens.total === null → undefined and cost === null → 0 at read time so existing corrupted rows no longer crash the message API. --- packages/opencode/src/session/message-v2.ts | 16 ++++++++++++---- packages/opencode/src/session/session.ts | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 1bea9f52c3ec..bacaa4113b80 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -77,12 +77,20 @@ export const cursor = { }, } -const info = (row: typeof MessageTable.$inferSelect) => - ({ - ...row.data, +const info = (row: typeof MessageTable.$inferSelect) => { + const data = row.data as Record + // JSON.stringify(NaN) → null in SQLite; on read-back null violates Schema.optional(Schema.Finite). + // Sanitize affected numeric fields so existing corrupted rows don't cause 500s. + if (data.tokens) { + if (data.tokens.total === null) data.tokens.total = undefined + } + if (data.cost === null) data.cost = 0 + return { + ...data, id: row.id, sessionID: row.session_id, - }) as Info + } as Info +} const part = (row: typeof PartTable.$inferSelect) => ({ diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index de8c3dc4cbd1..48ee159a5b84 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -366,9 +366,10 @@ export const getUsage = (input: { model: Provider.Model; usage: Usage; metadata? const adjustedInputTokens = safe(inputTokens - cacheReadInputTokens - cacheWriteInputTokens) const total = input.usage.totalTokens + const safeTotal = total !== undefined && Number.isFinite(total) ? Math.max(0, total) : undefined const tokens = { - total, + total: safeTotal, input: adjustedInputTokens, output: safe(outputTokens - reasoningTokens), reasoning: reasoningTokens,