From bf8921f988404b723c0bc7778c5336db732a63aa Mon Sep 17 00:00:00 2001 From: truffle Date: Fri, 1 May 2026 04:10:00 +0000 Subject: [PATCH] fix(cli): tolerate pre-existing plan directory on OneDrive (#9755) --- .../opencode/src/kilocode/session/prompt.ts | 12 ++++++- .../test/kilocode/ensure-plan-dir.test.ts | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/kilocode/ensure-plan-dir.test.ts diff --git a/packages/opencode/src/kilocode/session/prompt.ts b/packages/opencode/src/kilocode/session/prompt.ts index d939e9c704d..1abb51954ca 100644 --- a/packages/opencode/src/kilocode/session/prompt.ts +++ b/packages/opencode/src/kilocode/session/prompt.ts @@ -133,6 +133,16 @@ export namespace KiloSessionPrompt { } } + /** + * Ensures the plan file directory exists. Pre-checks with `Filesystem.isDir` + * because `fs.mkdir(recursive: true)` still throws `EEXIST` on Windows + * OneDrive ReparsePoint directories in some Node versions (kilocode#9755). + */ + export async function ensurePlanDir(dir: string) { + if (await Filesystem.isDir(dir)) return + await fs.mkdir(dir, { recursive: true }) + } + /** * Injects plan-specific reminders into the user message when using the plan agent. * Ensures the plan file directory exists and tells the agent where to write. @@ -145,7 +155,7 @@ export namespace KiloSessionPrompt { if (input.agent.name !== "plan") return const plan = Session.plan(input.session) const exists = await Filesystem.exists(plan) - if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true }) + if (!exists) await ensurePlanDir(path.dirname(plan)) const info = exists ? `A plan file already exists at ${plan}. You can read it and make incremental edits using the edit tool.` : `No plan file exists yet. You should create your plan at ${plan} using the write tool.` diff --git a/packages/opencode/test/kilocode/ensure-plan-dir.test.ts b/packages/opencode/test/kilocode/ensure-plan-dir.test.ts new file mode 100644 index 00000000000..b5293b76aef --- /dev/null +++ b/packages/opencode/test/kilocode/ensure-plan-dir.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from "bun:test" +import fs from "fs/promises" +import path from "path" +import { KiloSessionPrompt } from "../../src/kilocode/session/prompt" +import { tmpdir } from "../fixture/fixture" + +describe("KiloSessionPrompt.ensurePlanDir", () => { + test("creates a missing plan directory", async () => { + await using tmp = await tmpdir({}) + const dir = path.join(tmp.path, ".kilo", "plans") + await KiloSessionPrompt.ensurePlanDir(dir) + const stat = await fs.stat(dir) + expect(stat.isDirectory()).toBe(true) + }) + + test("is idempotent when the directory already exists", async () => { + await using tmp = await tmpdir({}) + const dir = path.join(tmp.path, ".kilo", "plans") + await fs.mkdir(dir, { recursive: true }) + await expect(KiloSessionPrompt.ensurePlanDir(dir)).resolves.toBeUndefined() + const stat = await fs.stat(dir) + expect(stat.isDirectory()).toBe(true) + }) + + test("creates intermediate parent directories", async () => { + await using tmp = await tmpdir({}) + const dir = path.join(tmp.path, "deep", "nested", ".kilo", "plans") + await KiloSessionPrompt.ensurePlanDir(dir) + const stat = await fs.stat(dir) + expect(stat.isDirectory()).toBe(true) + }) +})