diff --git a/src/config.ts b/src/config.ts
index 19ad4561..f916b0ec 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -20,7 +20,10 @@ function getConfigPath(): string {
 export async function readConfig(): Promise<CodeburnConfig> {
   try {
     const raw = await readFile(getConfigPath(), 'utf-8')
-    return JSON.parse(raw) as CodeburnConfig
+    const parsed = JSON.parse(raw)
+    if (!parsed || typeof parsed !== 'object') return {}
+    if (parsed.currency && typeof parsed.currency.code !== 'string') delete parsed.currency
+    return parsed as CodeburnConfig
   } catch {
     return {}
   }
diff --git a/src/day-aggregator.ts b/src/day-aggregator.ts
index 5030f8d8..79cd5c64 100644
--- a/src/day-aggregator.ts
+++ b/src/day-aggregator.ts
@@ -34,12 +34,14 @@ export function aggregateProjectsIntoDays(projects: ProjectSummary[]): DailyEntr
 
   for (const project of projects) {
     for (const session of project.sessions) {
-      const sessionDate = dateKey(session.firstTimestamp)
-      ensure(sessionDate).sessions += 1
+      const sessionDate = dateKey(session.firstTimestamp || '')
+      if (sessionDate) ensure(sessionDate).sessions += 1
 
       for (const turn of session.turns) {
         if (turn.assistantCalls.length === 0) continue
-        const turnDate = dateKey(turn.assistantCalls[0]!.timestamp)
+        const rawTs = turn.assistantCalls[0]!.timestamp
+        if (!rawTs) continue
+        const turnDate = dateKey(rawTs)
         const turnDay = ensure(turnDate)
 
         const editTurns = turn.hasEdits ? 1 : 0
diff --git a/src/export.ts b/src/export.ts
index d07f08d8..1ac34816 100644
--- a/src/export.ts
+++ b/src/export.ts
@@ -5,7 +5,7 @@ import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types
 import { getCurrency, convertCost } from './currency.js'
 
 function escCsv(s: string): string {
-  const sanitized = /^[=+\-@]/.test(s) ? `'${s}` : s
+  const sanitized = /^[\t\r=+\-@]/.test(s) ? `'${s}` : s
   if (sanitized.includes(',') || sanitized.includes('"') || sanitized.includes('\n')) {
     return `"${sanitized.replace(/"/g, '""')}"`
   }
@@ -281,7 +281,7 @@ async function clearCodeburnExportFolder(path: string): Promise<void> {
 /// wipe a sensitive file (prior versions did `rm(path, { force: true })` unconditionally).
 export async function exportCsv(periods: PeriodExport[], outputPath: string): Promise<string> {
   const thirtyDays = periods.find(p => p.label === '30 Days')
-  const thirtyDayProjects = thirtyDays?.projects ?? periods[periods.length - 1].projects
+  const thirtyDayProjects = thirtyDays?.projects ?? periods[periods.length - 1]?.projects ?? []
 
   let folder = resolve(outputPath)
   if (folder.toLowerCase().endsWith('.csv')) {
@@ -323,7 +323,7 @@ export async function exportCsv(periods: PeriodExport[], outputPath: string): Pr
 
 export async function exportJson(periods: PeriodExport[], outputPath: string): Promise<string> {
   const thirtyDays = periods.find(p => p.label === '30 Days')
-  const thirtyDayProjects = thirtyDays?.projects ?? periods[periods.length - 1].projects
+  const thirtyDayProjects = thirtyDays?.projects ?? periods[periods.length - 1]?.projects ?? []
   const { code, rate, symbol } = getCurrency()
 
   const data = {
diff --git a/src/fs-utils.ts b/src/fs-utils.ts
index 49eff20b..823a630a 100644
--- a/src/fs-utils.ts
+++ b/src/fs-utils.ts
@@ -89,5 +89,7 @@ export async function* readSessionLines(filePath: string): AsyncGenerator<string
     for await (const line of rl) yield line
   } catch (err) {
     warn(`stream read failed for ${filePath}: ${(err as NodeJS.ErrnoException).code ?? 'unknown'}`)
+  } finally {
+    stream.destroy()
   }
 }
diff --git a/src/models.ts b/src/models.ts
index 1eaaf2ce..d1c12b6f 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -62,7 +62,7 @@ function getCachePath(): string {
 }
 
 function parseLiteLLMEntry(entry: LiteLLMEntry): ModelCosts | null {
-  if (!entry.input_cost_per_token || !entry.output_cost_per_token) return null
+  if (entry.input_cost_per_token === undefined || entry.output_cost_per_token === undefined) return null
   return {
     inputCostPerToken: entry.input_cost_per_token,
     outputCostPerToken: entry.output_cost_per_token,
@@ -140,7 +140,7 @@ export function getModelCosts(model: string): ModelCosts | null {
   }
 
   for (const [key, costs] of pricingCache ?? new Map()) {
-    if (canonical.startsWith(key) || key.startsWith(canonical)) return costs
+    if (canonical.startsWith(key)) return costs
   }
 
   for (const [key, costs] of Object.entries(FALLBACK_PRICING)) {
@@ -202,7 +202,10 @@ export function getShortModelName(model: string): string {
     'o4-mini': 'o4-mini',
     'o3': 'o3',
   }
-  for (const [key, name] of Object.entries(shortNames)) {
+  // Sort by key length descending so longer prefixes match first
+  // (e.g., 'gpt-4o-mini' before 'gpt-4o')
+  const sorted = Object.entries(shortNames).sort((a, b) => b[0].length - a[0].length)
+  for (const [key, name] of sorted) {
     if (canonical.startsWith(key)) return name
   }
   return canonical
diff --git a/src/optimize.ts b/src/optimize.ts
index 2e8913c7..748e0c5f 100644
--- a/src/optimize.ts
+++ b/src/optimize.ts
@@ -448,7 +448,7 @@ export function detectDuplicateReads(calls: ToolCall[], dateRange?: DateRange):
       totalDuplicates += extra
       if (entry.recent > 1) recentDuplicates += entry.recent - 1
       const name = basename(file)
-      fileDupes.set(name, (fileDupes.get(name) ?? 0) + extra)
+      fileDupes.set(name, (fileDupes.get(name) ?? 0) + entry.count)
     }
   }
 
@@ -461,7 +461,7 @@ export function detectDuplicateReads(calls: ToolCall[], dateRange?: DateRange):
   const worst = [...fileDupes.entries()]
     .sort((a, b) => b[1] - a[1])
     .slice(0, TOP_ITEMS_PREVIEW)
-    .map(([name, n]) => `${name} (${n + 1}x)`)
+    .map(([name, n]) => `${name} (${n}x)`)
     .join(', ')
 
   const tokensSaved = totalDuplicates * AVG_TOKENS_PER_READ
diff --git a/src/parser.ts b/src/parser.ts
index 4adcf3bc..fcb2fabc 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -173,7 +173,7 @@ function buildSessionSummary(
   const toolBreakdown: SessionSummary['toolBreakdown'] = Object.create(null)
   const mcpBreakdown: SessionSummary['mcpBreakdown'] = Object.create(null)
   const bashBreakdown: SessionSummary['bashBreakdown'] = Object.create(null)
-  const categoryBreakdown: SessionSummary['categoryBreakdown'] = {} as SessionSummary['categoryBreakdown']
+  const categoryBreakdown: SessionSummary['categoryBreakdown'] = Object.create(null) as SessionSummary['categoryBreakdown']
 
   let totalCost = 0
   let totalInput = 0
diff --git a/src/providers/codex.ts b/src/providers/codex.ts
index 01d48b70..eb3f836b 100644
--- a/src/providers/codex.ts
+++ b/src/providers/codex.ts
@@ -15,6 +15,9 @@ const modelDisplayNames: Record<string, string> = {
   'gpt-4o': 'GPT-4o',
 }
 
+// Sort by key length descending so longer prefixes match first
+const modelDisplayEntries = Object.entries(modelDisplayNames).sort((a, b) => b[0].length - a[0].length)
+
 const toolNameMap: Record<string, string> = {
   exec_command: 'Bash',
   read_file: 'Read',
@@ -205,10 +208,10 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
           } else if (cumulativeTotal > 0) {
             const total = info.total_token_usage
             if (!total) continue
-            inputTokens = (total.input_tokens ?? 0) - prevInput
-            cachedInputTokens = (total.cached_input_tokens ?? 0) - prevCached
-            outputTokens = (total.output_tokens ?? 0) - prevOutput
-            reasoningTokens = (total.reasoning_output_tokens ?? 0) - prevReasoning
+            inputTokens = Math.max(0, (total.input_tokens ?? 0) - prevInput)
+            cachedInputTokens = Math.max(0, (total.cached_input_tokens ?? 0) - prevCached)
+            outputTokens = Math.max(0, (total.output_tokens ?? 0) - prevOutput)
+            reasoningTokens = Math.max(0, (total.reasoning_output_tokens ?? 0) - prevReasoning)
           }
 
           if (!last) {
@@ -280,7 +283,7 @@ export function createCodexProvider(codexDir?: string): Provider {
     displayName: 'Codex',
 
     modelDisplayName(model: string): string {
-      for (const [key, name] of Object.entries(modelDisplayNames)) {
+      for (const [key, name] of modelDisplayEntries) {
         if (model.startsWith(key)) return name
       }
       return model
