From 9b8a7da1e6bdc972b912d67502ae93a191836712 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 15 Nov 2025 22:50:13 -0800 Subject: [PATCH] fix: history jsonl file corruption cases (#4364) --- .../cli/cmd/tui/component/prompt/history.tsx | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index 4b02d558..4fd60dd3 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -4,7 +4,7 @@ import { onMount } from "solid-js" import { createStore, produce } from "solid-js/store" import { clone } from "remeda" import { createSimpleContext } from "../../context/helper" -import { appendFile } from "fs/promises" +import { appendFile, writeFile } from "fs/promises" import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk" export type PromptInfo = { @@ -24,6 +24,8 @@ export type PromptInfo = { )[] } +const MAX_HISTORY_ENTRIES = 50 + export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({ name: "PromptHistory", init: () => { @@ -33,8 +35,23 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create const lines = text .split("\n") .filter(Boolean) - .map((line) => JSON.parse(line)) - setStore("history", lines as PromptInfo[]) + .map((line) => { + try { + return JSON.parse(line) + } catch { + return null + } + }) + .filter((line): line is PromptInfo => line !== null) + .slice(-MAX_HISTORY_ENTRIES) + + setStore("history", lines) + + // Rewrite file with only valid entries to self-heal corruption + if (lines.length > 0) { + const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n" + writeFile(historyFile.name!, content).catch(() => {}) + } }) const [store, setStore] = createStore({ @@ -64,14 +81,26 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create return store.history.at(store.index) }, append(item: PromptInfo) { - item = clone(item) - appendFile(historyFile.name!, JSON.stringify(item) + "\n") + const entry = clone(item) + let trimmed = false setStore( produce((draft) => { - draft.history.push(item) + draft.history.push(entry) + if (draft.history.length > MAX_HISTORY_ENTRIES) { + draft.history = draft.history.slice(-MAX_HISTORY_ENTRIES) + trimmed = true + } draft.index = 0 }), ) + + if (trimmed) { + const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n" + writeFile(historyFile.name!, content).catch(() => {}) + return + } + + appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => {}) }, } },