diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index b90c6139..e9704fe4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `.pi` folder being created unnecessarily when only reading settings. The folder is now only created when writing project-specific settings. + ## [0.54.1] - 2026-02-22 ### Fixed diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index f6ac85e2..b3cd8863 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -147,16 +147,24 @@ export class FileSettingsStorage implements SettingsStorage { withLock(scope: SettingsScope, fn: (current: string | undefined) => string | undefined): void { const path = scope === "global" ? this.globalSettingsPath : this.projectSettingsPath; const dir = dirname(path); - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } let release: (() => void) | undefined; try { - release = lockfile.lockSync(path, { realpath: false }); - const current = existsSync(path) ? readFileSync(path, "utf-8") : undefined; + // Only create directory and lock if file exists or we need to write + const fileExists = existsSync(path); + if (fileExists) { + release = lockfile.lockSync(path, { realpath: false }); + } + const current = fileExists ? readFileSync(path, "utf-8") : undefined; const next = fn(current); if (next !== undefined) { + // Only create directory when we actually need to write + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + if (!release) { + release = lockfile.lockSync(path, { realpath: false }); + } writeFileSync(path, next, "utf-8"); } } finally { diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index df8199e2..263dac6e 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -212,6 +212,50 @@ describe("SettingsManager", () => { }); }); + describe("project settings directory creation", () => { + it("should not create .pi folder when only reading project settings", () => { + // Create agent dir with global settings, but NO .pi folder in project + const settingsPath = join(agentDir, "settings.json"); + writeFileSync(settingsPath, JSON.stringify({ theme: "dark" })); + + // Delete the .pi folder that beforeEach created + rmSync(join(projectDir, ".pi"), { recursive: true }); + + // Create SettingsManager (reads both global and project settings) + const manager = SettingsManager.create(projectDir, agentDir); + + // .pi folder should NOT have been created just from reading + expect(existsSync(join(projectDir, ".pi"))).toBe(false); + + // Settings should still be loaded from global + expect(manager.getTheme()).toBe("dark"); + }); + + it("should create .pi folder when writing project settings", async () => { + // Create agent dir with global settings, but NO .pi folder in project + const settingsPath = join(agentDir, "settings.json"); + writeFileSync(settingsPath, JSON.stringify({ theme: "dark" })); + + // Delete the .pi folder that beforeEach created + rmSync(join(projectDir, ".pi"), { recursive: true }); + + const manager = SettingsManager.create(projectDir, agentDir); + + // .pi folder should NOT exist yet + expect(existsSync(join(projectDir, ".pi"))).toBe(false); + + // Write a project-specific setting + manager.setProjectPackages([{ source: "npm:test-pkg" }]); + await manager.flush(); + + // Now .pi folder should exist + expect(existsSync(join(projectDir, ".pi"))).toBe(true); + + // And settings file should be created + expect(existsSync(join(projectDir, ".pi", "settings.json"))).toBe(true); + }); + }); + describe("shellCommandPrefix", () => { it("should load shellCommandPrefix from settings", () => { const settingsPath = join(agentDir, "settings.json");