From 4b8a5fd115143134ebe8d81841c1a98f5db35381 Mon Sep 17 00:00:00 2001 From: umutcagand Date: Mon, 24 Aug 2026 00:28:31 +0300 Subject: [PATCH 1/2] fix(cli): survive unavailable chat log directory --- cli/src/utils/logger.ts | 46 +++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/cli/src/utils/logger.ts b/cli/src/utils/logger.ts index 1a4b5270e5..d44f3b4acf 100644 --- a/cli/src/utils/logger.ts +++ b/cli/src/utils/logger.ts @@ -100,6 +100,45 @@ function setLogPath(p: string): void { ) } +/** + * Resolve the per-run log destination without allowing filesystem failures to + * take down the CLI during startup. + * + * In production, resolving the destination creates the current chat + * directory. That directory may be unavailable (for example, when the + * config directory is read-only), so callers must treat an absent destination + * as "continue without file logging". + */ +export function resolveLogTarget(params: { + projectRoot: string + isDev: boolean + getCurrentChatDir: () => string +}): string | undefined { + try { + return params.isDev + ? path.join(params.projectRoot, 'debug', 'cli.jsonl') + : path.join(params.getCurrentChatDir(), CHAT_LOG_FILENAME) + } catch { + return undefined + } +} + +function trySetLogPath(projectRoot: string): void { + const logTarget = resolveLogTarget({ + projectRoot, + isDev: IS_DEV, + getCurrentChatDir, + }) + if (!logTarget) return + + try { + setLogPath(logTarget) + } catch { + // File logging is best-effort and must never prevent the CLI from + // starting when the config or chat directory cannot be written. + } +} + export function clearLogFile(): void { const projectRoot = getProjectRoot() const debugDir = path.join(projectRoot, 'debug') @@ -139,12 +178,7 @@ function sendAnalyticsAndLog( projectRoot = undefined } if (projectRoot) { - const logTarget = - IS_DEV - ? path.join(projectRoot, 'debug', 'cli.jsonl') - : path.join(getCurrentChatDir(), CHAT_LOG_FILENAME) - - setLogPath(logTarget) + trySetLogPath(projectRoot) } } From 147cd7d75cca83ec890d8c4611010108407efff8 Mon Sep 17 00:00:00 2001 From: umutcagand Date: Mon, 24 Aug 2026 00:28:32 +0300 Subject: [PATCH 2/2] test(cli): cover logger startup fallback --- cli/src/utils/__tests__/logger.test.ts | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 cli/src/utils/__tests__/logger.test.ts diff --git a/cli/src/utils/__tests__/logger.test.ts b/cli/src/utils/__tests__/logger.test.ts new file mode 100644 index 0000000000..3cc83c4fb5 --- /dev/null +++ b/cli/src/utils/__tests__/logger.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from 'bun:test' + +import path from 'path' + +import { CHAT_LOG_FILENAME, resolveLogTarget } from '../logger' + +describe('resolveLogTarget', () => { + test('uses the project debug log in development', () => { + let currentChatDirCalls = 0 + + const target = resolveLogTarget({ + projectRoot: '/project', + isDev: true, + getCurrentChatDir: () => { + currentChatDirCalls += 1 + return '/chat' + }, + }) + + expect(target).toBe(path.join('/project', 'debug', 'cli.jsonl')) + expect(currentChatDirCalls).toBe(0) + }) + + test('uses the current chat log in production', () => { + const target = resolveLogTarget({ + projectRoot: '/project', + isDev: false, + getCurrentChatDir: () => '/chat/2026-01-01T00-00-00.000Z', + }) + + expect(target).toBe( + path.join('/chat/2026-01-01T00-00-00.000Z', CHAT_LOG_FILENAME), + ) + }) + + test('skips file logging when the chat directory cannot be created', () => { + const target = resolveLogTarget({ + projectRoot: '/project', + isDev: false, + getCurrentChatDir: () => { + throw new Error('EACCES') + }, + }) + + expect(target).toBeUndefined() + }) +})