From 0347f5a973577c0888d9013babbc1fa98304c459 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 2 Apr 2026 18:56:13 -0400 Subject: [PATCH] fix(image-analysis): stabilize mcp config locking --- src/utils/image-analysis/mcp-installer.ts | 11 ++++++++--- tests/unit/utils/image-analysis/mcp-installer.test.ts | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/utils/image-analysis/mcp-installer.ts b/src/utils/image-analysis/mcp-installer.ts index 5dc5ed5e..76b8c3d2 100644 --- a/src/utils/image-analysis/mcp-installer.ts +++ b/src/utils/image-analysis/mcp-installer.ts @@ -121,11 +121,16 @@ function writeClaudeUserConfig(configPath: string, config: ClaudeUserConfig): bo } function withClaudeUserConfigLock(configPath: string, callback: () => T): T { - const lockTarget = path.dirname(configPath); + const configDir = path.dirname(configPath); + const lockTarget = path.join(configDir, `${path.basename(configPath)}.ccs-lock`); let release: (() => void) | undefined; - if (!fs.existsSync(path.dirname(configPath))) { - fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 }); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); + } + + if (!fs.existsSync(lockTarget)) { + fs.writeFileSync(lockTarget, '', { encoding: 'utf8', mode: 0o600 }); } try { diff --git a/tests/unit/utils/image-analysis/mcp-installer.test.ts b/tests/unit/utils/image-analysis/mcp-installer.test.ts index cc82e884..40b617f5 100644 --- a/tests/unit/utils/image-analysis/mcp-installer.test.ts +++ b/tests/unit/utils/image-analysis/mcp-installer.test.ts @@ -191,7 +191,7 @@ describe('ensureImageAnalysisMcp', () => { expect(ensureImageAnalysisMcp()).toBe(true); expect(lockSpy).toHaveBeenCalled(); - expect(lockSpy.mock.calls[0]?.[0]).toBe(tempHome as string); + expect(lockSpy.mock.calls[0]?.[0]).toBe(path.join(tempHome as string, '.claude.json.ccs-lock')); }); it('returns false instead of throwing when ~/.claude.json is already locked', () => { @@ -200,8 +200,11 @@ describe('ensureImageAnalysisMcp', () => { const claudeUserConfigPath = path.join(tempHome as string, '.claude.json'); fs.writeFileSync(claudeUserConfigPath, '{}\n', 'utf8'); + fs.writeFileSync(path.join(tempHome as string, '.claude.json.ccs-lock'), '', 'utf8'); - const release = lockfile.lockSync(tempHome as string, { stale: 10000 }) as () => void; + const release = lockfile.lockSync(path.join(tempHome as string, '.claude.json.ccs-lock'), { + stale: 10000, + }) as () => void; try { let result: boolean | undefined;