From 575e7ae9a5dfe0d79fae7cf07a4b7e0d1fea58bc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 02:17:00 +0000 Subject: [PATCH 1/3] fix: four code-quality and correctness bugs found in review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sessions: fastestResponseMs migrated from Infinity→null (JSON.stringify serialises Infinity as null); the migration guard missed the null case so the field stayed null forever after a restart, causing the fastest-time tracker to never update - stream: remove duplicate normalizeForDedupe / isDuplicateOfDelivered implementations; re-export from the canonical shared/delivered-text module instead — eliminates the maintenance risk of two diverging copies (the stream.ts copy also had a weaker string[] vs readonly string[] signature) - terminal/input: close() now resolves and clears a pending waitForInput promise instead of leaving it hanging forever - handler: log swallowed onToolUse callback errors at warn level rather than silently discarding them, making debugging callback issues possible https://claude.ai/code/session_01L13ce8EKaoTNrjgw4HRvy3 --- src/backend/claude-sdk/handler.ts | 4 +-- src/backend/claude-sdk/stream.ts | 41 +++++-------------------------- src/frontend/terminal/input.ts | 5 ++++ src/storage/sessions.ts | 3 ++- 4 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/backend/claude-sdk/handler.ts b/src/backend/claude-sdk/handler.ts index 0f7190bee..5c11b618d 100644 --- a/src/backend/claude-sdk/handler.ts +++ b/src/backend/claude-sdk/handler.ts @@ -154,8 +154,8 @@ export async function handleMessage( if (onToolUse) { try { onToolUse(tool.name, tool.input); - } catch { - /* non-fatal */ + } catch (err) { + logWarn("agent", `onToolUse callback threw for ${tool.name}: ${err instanceof Error ? err.message : err}`); } } } diff --git a/src/backend/claude-sdk/stream.ts b/src/backend/claude-sdk/stream.ts index 2e74b6b1f..f5f6e6043 100644 --- a/src/backend/claude-sdk/stream.ts +++ b/src/backend/claude-sdk/stream.ts @@ -256,38 +256,9 @@ export function processResultMessage( } // ── Trailing-text fallback dedup ──────────────────────────────────────────── - -/** - * Normalize text for fuzzy comparison — trim, lowercase, collapse whitespace, - * strip emoji. Used to detect whether trailing prose duplicates content - * already delivered via `end_turn` / `send(type="text")`. - */ -export function normalizeForDedupe(text: string): string { - return text - .trim() - .toLowerCase() - .replace(/\p{Emoji_Presentation}|\p{Extended_Pictographic}/gu, "") - .replace(/\s+/g, " ") - .trim(); -} - -const MIN_DEDUP_LENGTH = 10; - -/** - * Returns true if `candidate` is substantively the same as any text in - * `deliveredNorms`. "Substantively" = one is a substring of the other after - * normalization; both must be at least MIN_DEDUP_LENGTH chars to avoid - * dropping short legitimate replies. - */ -export function isDuplicateOfDelivered( - candidate: string, - deliveredNorms: string[], -): boolean { - if (deliveredNorms.length === 0) return false; - const norm = normalizeForDedupe(candidate); - if (norm.length < MIN_DEDUP_LENGTH) return false; - return deliveredNorms.some( - (d) => - d.length >= MIN_DEDUP_LENGTH && (norm.includes(d) || d.includes(norm)), - ); -} +// Re-exported from the shared module so that consumers importing from +// stream.ts continue to work without maintaining a separate implementation. +export { + normalizeForDedupe, + isDuplicateOfDelivered, +} from "../shared/delivered-text.js"; diff --git a/src/frontend/terminal/input.ts b/src/frontend/terminal/input.ts index 60e0a588f..6dc6a26fd 100644 --- a/src/frontend/terminal/input.ts +++ b/src/frontend/terminal/input.ts @@ -289,6 +289,11 @@ export function createInput(promptStr: string): InputHandler { paused = false; }, close() { + if (pendingResolve) { + const resolve = pendingResolve; + pendingResolve = null; + resolve(""); + } process.stdout.write("\x1b[?2004l"); if (process.stdin.isTTY) process.stdin.setRawMode(false); process.stdin.pause(); diff --git a/src/storage/sessions.ts b/src/storage/sessions.ts index 65f44669c..09c565a41 100644 --- a/src/storage/sessions.ts +++ b/src/storage/sessions.ts @@ -156,7 +156,8 @@ export function getSession(chatId: string): SessionState { session.usage.lastResponseMs = 0; if ( session.usage.fastestResponseMs === undefined || - session.usage.fastestResponseMs === 0 + session.usage.fastestResponseMs === 0 || + session.usage.fastestResponseMs === null ) session.usage.fastestResponseMs = Infinity; // Migrate sessions from before context tracking was added From 89a45c744afc4840991c6be10bc80507733a53fd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 02:17:54 +0000 Subject: [PATCH 2/3] chore: update package-lock.json after npm install https://claude.ai/code/session_01L13ce8EKaoTNrjgw4HRvy3 --- package-lock.json | 268 +++++++++++++++++++++++----------------------- 1 file changed, 134 insertions(+), 134 deletions(-) diff --git a/package-lock.json b/package-lock.json index 333a5b939..9c469f945 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1114,6 +1114,140 @@ "@emnapi/runtime": "^1.7.1" } }, + "node_modules/@openai/codex": { + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0.tgz", + "integrity": "sha512-WGDj+RZ3TXWC/7MlwprgLWOqzpwatPIINPhP3IRzHA0ni+o3QZ4i4xrS2uWwGmHUJ395J5JHwoZAAZYyfJyz6w==", + "license": "Apache-2.0", + "bin": { + "codex": "bin/codex.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@openai/codex-darwin-arm64": "npm:@openai/codex@0.130.0-darwin-arm64", + "@openai/codex-darwin-x64": "npm:@openai/codex@0.130.0-darwin-x64", + "@openai/codex-linux-arm64": "npm:@openai/codex@0.130.0-linux-arm64", + "@openai/codex-linux-x64": "npm:@openai/codex@0.130.0-linux-x64", + "@openai/codex-win32-arm64": "npm:@openai/codex@0.130.0-win32-arm64", + "@openai/codex-win32-x64": "npm:@openai/codex@0.130.0-win32-x64" + } + }, + "node_modules/@openai/codex-darwin-arm64": { + "name": "@openai/codex", + "version": "0.130.0-darwin-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-darwin-arm64.tgz", + "integrity": "sha512-R9pkGC7kwC8yQ8el5hvBlmugQlcsG/pHMEFgZluu03X9fD2TezGxdq3KqRDRCZuMYl07ILamVEoqknuJ0cq7MA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-darwin-x64": { + "name": "@openai/codex", + "version": "0.130.0-darwin-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-darwin-x64.tgz", + "integrity": "sha512-gJ+7J8djevgtdra+NgDAiQQPW+O3KTsgGfE3E5dpDfww3zS5OCeV0V2dhxqnJdlOjOSDw99o0P2LqBv19mhpRw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-linux-arm64": { + "name": "@openai/codex", + "version": "0.130.0-linux-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-linux-arm64.tgz", + "integrity": "sha512-tFtH0V9/hEI3d9y7zP92BXI9FM4Z3+STNQaOR52Czv18TRtCFUp7CbIUYaToopuq6UBfnE1VKr8RLhwT5FcbmA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-linux-x64": { + "name": "@openai/codex", + "version": "0.130.0-linux-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-linux-x64.tgz", + "integrity": "sha512-3VcNlez99xdnEf+kB1IOpWv9fICYV9PiGj4sLCO4TCcShLnyxe+YBGa3poknkvXLnMG0qiN9SMnYS2FGrMxQcA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-sdk": { + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.130.0.tgz", + "integrity": "sha512-ICKaZ5zrIDg71AiQcsUToVoe5Icmrc3LwSM5+2z7Cf8F1x6nOaY7/ucpFlr4aH8oDe7t3dangc+MsWZTkdvDFw==", + "license": "Apache-2.0", + "dependencies": { + "@openai/codex": "0.130.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@openai/codex-win32-arm64": { + "name": "@openai/codex", + "version": "0.130.0-win32-arm64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-win32-arm64.tgz", + "integrity": "sha512-vdpmiNp57L/arZabltLXn8TyEtNa7W1meOEkr+3R6W/8ZyBt++wuqz1Orv134OT2grrcFJsIVCAIPiqUxCvBkA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@openai/codex-win32-x64": { + "name": "@openai/codex", + "version": "0.130.0-win32-x64", + "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-win32-x64.tgz", + "integrity": "sha512-FzMznm7fr5/nbjZgOujZ9Y9AbdGm7ji1FOoWiY3U+srqauvZaTgn6o6aCheSL7kuymu7nTLOO/cAyWV6NuesqQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@opencode-ai/sdk": { "version": "1.14.48", "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.14.48.tgz", @@ -6404,140 +6538,6 @@ "peerDependencies": { "zod": "^3.25.28 || ^4" } - }, - "node_modules/@openai/codex": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0.tgz", - "integrity": "sha512-WGDj+RZ3TXWC/7MlwprgLWOqzpwatPIINPhP3IRzHA0ni+o3QZ4i4xrS2uWwGmHUJ395J5JHwoZAAZYyfJyz6w==", - "license": "Apache-2.0", - "bin": { - "codex": "bin/codex.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@openai/codex-darwin-arm64": "npm:@openai/codex@0.130.0-darwin-arm64", - "@openai/codex-darwin-x64": "npm:@openai/codex@0.130.0-darwin-x64", - "@openai/codex-linux-arm64": "npm:@openai/codex@0.130.0-linux-arm64", - "@openai/codex-linux-x64": "npm:@openai/codex@0.130.0-linux-x64", - "@openai/codex-win32-arm64": "npm:@openai/codex@0.130.0-win32-arm64", - "@openai/codex-win32-x64": "npm:@openai/codex@0.130.0-win32-x64" - } - }, - "node_modules/@openai/codex-darwin-arm64": { - "name": "@openai/codex", - "version": "0.130.0-darwin-arm64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-darwin-arm64.tgz", - "integrity": "sha512-R9pkGC7kwC8yQ8el5hvBlmugQlcsG/pHMEFgZluu03X9fD2TezGxdq3KqRDRCZuMYl07ILamVEoqknuJ0cq7MA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-darwin-x64": { - "name": "@openai/codex", - "version": "0.130.0-darwin-x64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-darwin-x64.tgz", - "integrity": "sha512-gJ+7J8djevgtdra+NgDAiQQPW+O3KTsgGfE3E5dpDfww3zS5OCeV0V2dhxqnJdlOjOSDw99o0P2LqBv19mhpRw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-linux-arm64": { - "name": "@openai/codex", - "version": "0.130.0-linux-arm64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-linux-arm64.tgz", - "integrity": "sha512-tFtH0V9/hEI3d9y7zP92BXI9FM4Z3+STNQaOR52Czv18TRtCFUp7CbIUYaToopuq6UBfnE1VKr8RLhwT5FcbmA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-linux-x64": { - "name": "@openai/codex", - "version": "0.130.0-linux-x64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-linux-x64.tgz", - "integrity": "sha512-3VcNlez99xdnEf+kB1IOpWv9fICYV9PiGj4sLCO4TCcShLnyxe+YBGa3poknkvXLnMG0qiN9SMnYS2FGrMxQcA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-sdk": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.130.0.tgz", - "integrity": "sha512-ICKaZ5zrIDg71AiQcsUToVoe5Icmrc3LwSM5+2z7Cf8F1x6nOaY7/ucpFlr4aH8oDe7t3dangc+MsWZTkdvDFw==", - "license": "Apache-2.0", - "dependencies": { - "@openai/codex": "0.130.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@openai/codex-win32-arm64": { - "name": "@openai/codex", - "version": "0.130.0-win32-arm64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-win32-arm64.tgz", - "integrity": "sha512-vdpmiNp57L/arZabltLXn8TyEtNa7W1meOEkr+3R6W/8ZyBt++wuqz1Orv134OT2grrcFJsIVCAIPiqUxCvBkA==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-win32-x64": { - "name": "@openai/codex", - "version": "0.130.0-win32-x64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.130.0-win32-x64.tgz", - "integrity": "sha512-FzMznm7fr5/nbjZgOujZ9Y9AbdGm7ji1FOoWiY3U+srqauvZaTgn6o6aCheSL7kuymu7nTLOO/cAyWV6NuesqQ==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } } } } From a2ebd1d09e3f03684a2b8d128c01b130e3f96aef Mon Sep 17 00:00:00 2001 From: Talon Heartbeat Date: Sun, 17 May 2026 04:08:24 +0000 Subject: [PATCH 3/3] style: prettier fix for handler.ts (wrap long logWarn line) One line in the Bug 4 fix exceeded prettier's print-width. Wrapped `logWarn(...)` call to match project style. No logic changes. --- src/backend/claude-sdk/handler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/claude-sdk/handler.ts b/src/backend/claude-sdk/handler.ts index 5c11b618d..342943149 100644 --- a/src/backend/claude-sdk/handler.ts +++ b/src/backend/claude-sdk/handler.ts @@ -155,7 +155,10 @@ export async function handleMessage( try { onToolUse(tool.name, tool.input); } catch (err) { - logWarn("agent", `onToolUse callback threw for ${tool.name}: ${err instanceof Error ? err.message : err}`); + logWarn( + "agent", + `onToolUse callback threw for ${tool.name}: ${err instanceof Error ? err.message : err}`, + ); } } }