From 496c669f1d192a8472d1a04b5e1dc309fc604451 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Mon, 30 Mar 2026 15:29:40 +0200 Subject: [PATCH] fix: use correct path encoding for Cursor session-context on Windows Cursor strips drive-letter colons (C:\ -> C-Users-...) while Claude Code replaces them with dashes (C:\ -> C--Users-...). The shared encodeProjectPath only implemented the Claude Code scheme, causing "No Cursor agent-transcripts directory found" on Windows. Add a target option so each caller selects the right encoding. Made-with: Cursor --- src/helpers/session-context.ts | 26 +++++++++++++----- tests/helpers/session-context-cursor.test.ts | 2 +- tests/helpers/session-context.test.ts | 28 ++++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/helpers/session-context.ts b/src/helpers/session-context.ts index ddb080f..7d7c48b 100644 --- a/src/helpers/session-context.ts +++ b/src/helpers/session-context.ts @@ -2,24 +2,34 @@ import { readdirSync, statSync } from "node:fs"; import { homedir } from "node:os"; import { basename, join } from "node:path"; +import type { EditorTarget } from "./init-project"; import { isWSL, toWindowsPath } from "./platform"; /** * Encode a project root path into the directory name used by Claude/Cursor * for storing session files under `~/.claude/projects/` or `~/.cursor/projects/`. * - * Replaces path separators (`\`, `/`), drive-letter colons (`:`), and dots (`.`) - * with dashes (`-`) to match the encoding Claude Code and Cursor use internally. + * Replaces path separators (`\`, `/`) and dots (`.`) with dashes (`-`). + * Drive-letter colons (`:`) are handled per-tool: Claude Code replaces them + * with dashes while Cursor strips them entirely. * - * Examples: + * Examples (target = "claude", the default): * - `/home/user/project` → `-home-user-project` * - `C:\Users\user\project` → `C--Users-user-project` * - `E:\foo\.claude\worktrees\x` → `E--foo--claude-worktrees-x` * + * Examples (target = "cursor"): + * - `/home/user/project` → `-home-user-project` + * - `C:\Users\user\project` → `C-Users-user-project` + * - `E:\foo\.claude\worktrees\x` → `E-foo--claude-worktrees-x` + * * In WSL, converts to the Windows path first so the encoded name matches * what the Windows-side editor uses. */ -export async function encodeProjectPath(projectRoot: string): Promise { +export async function encodeProjectPath( + projectRoot: string, + target?: EditorTarget +): Promise { let raw = projectRoot; if (isWSL()) { const winPath = await toWindowsPath(projectRoot); @@ -27,10 +37,11 @@ export async function encodeProjectPath(projectRoot: string): Promise { raw = winPath; } } + const colonReplacement = target === "cursor" ? "" : "-"; return raw .replaceAll("\\", "-") .replaceAll("/", "-") - .replaceAll(":", "-") + .replaceAll(":", colonReplacement) .replaceAll(".", "-"); } @@ -177,7 +188,10 @@ export async function readCursorSession( options?: ReadCursorSessionOptions ): Promise { const limit = options?.maxEntries ?? 200; - const encodedPath = await encodeProjectPath(projectRoot ?? process.cwd()); + const encodedPath = await encodeProjectPath( + projectRoot ?? process.cwd(), + "cursor" + ); const transcriptsDir = join( homedir(), ".cursor", diff --git a/tests/helpers/session-context-cursor.test.ts b/tests/helpers/session-context-cursor.test.ts index a1aebfc..95f4d1d 100644 --- a/tests/helpers/session-context-cursor.test.ts +++ b/tests/helpers/session-context-cursor.test.ts @@ -16,7 +16,7 @@ describe("readCursorSession", () => { const encodedProject = projectRoot .replaceAll("/", "-") .replaceAll("\\", "-") - .replaceAll(":", "-") + .replaceAll(":", "") .replaceAll(".", "-"); let transcriptsDir: string; diff --git a/tests/helpers/session-context.test.ts b/tests/helpers/session-context.test.ts index 5da6662..31cb298 100644 --- a/tests/helpers/session-context.test.ts +++ b/tests/helpers/session-context.test.ts @@ -55,6 +55,34 @@ describe("encodeProjectPath", () => { ) ).toBe("E--archgate-cli--claude-worktrees-fancy-prancing-sedgewick"); }); + + test("cursor target strips colons instead of replacing with dashes", async () => { + expect(await encodeProjectPath("C:\\Users\\user\\project", "cursor")).toBe( + "C-Users-user-project" + ); + }); + + test("cursor target handles mixed slashes", async () => { + expect(await encodeProjectPath("C:\\Users/user\\project", "cursor")).toBe( + "C-Users-user-project" + ); + }); + + test("cursor target encodes Windows worktree path", async () => { + expect( + await encodeProjectPath( + "E:\\archgate\\cli\\.claude\\worktrees\\fancy-prancing-sedgewick", + "cursor" + ) + ).toBe("E-archgate-cli--claude-worktrees-fancy-prancing-sedgewick"); + }); + + test("cursor target produces same result as default for Unix paths", async () => { + const unixPath = "/home/user/project"; + expect(await encodeProjectPath(unixPath, "cursor")).toBe( + await encodeProjectPath(unixPath) + ); + }); }); describe("readClaudeCodeSession", () => {