From 37ae0d5851e827c40e3b35298d7ebfe284e8f740 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Sun, 22 Mar 2026 00:08:41 +0100 Subject: [PATCH] fix: encode colons and dots in session-context project path `encodeProjectPath` only replaced `\` and `/` with `-`, but Claude Code and Cursor also replace `:` and `.`. On Windows, this caused `session-context claude-code` to look up a directory like `E:-archgate-cli-.claude-worktrees-x` instead of the actual `E--archgate-cli--claude-worktrees-x`, resulting in "No session files found". Add `:` and `.` to the replacement set and update tests to cover the exact Windows worktree scenario that was failing. --- src/helpers/session-context.ts | 25 +++++++++++++++---- tests/helpers/session-context-cursor.test.ts | 6 ++++- tests/helpers/session-context.test.ts | 26 +++++++++++++++++--- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/helpers/session-context.ts b/src/helpers/session-context.ts index 2b5190e2..ddb080f5 100644 --- a/src/helpers/session-context.ts +++ b/src/helpers/session-context.ts @@ -5,18 +5,33 @@ import { basename, join } from "node:path"; import { isWSL, toWindowsPath } from "./platform"; /** - * Encode a project root path into the directory name used by Claude/Cursor. - * In WSL, converts to Windows path first so the encoded name matches - * what Windows-side Claude/Cursor uses. + * 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. + * + * Examples: + * - `/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 { + let raw = projectRoot; if (isWSL()) { const winPath = await toWindowsPath(projectRoot); if (winPath) { - return winPath.replaceAll("\\", "-").replaceAll("/", "-"); + raw = winPath; } } - return projectRoot.replaceAll("\\", "-").replaceAll("/", "-"); + return raw + .replaceAll("\\", "-") + .replaceAll("/", "-") + .replaceAll(":", "-") + .replaceAll(".", "-"); } const RELEVANT_TYPES = new Set(["user", "assistant"]); diff --git a/tests/helpers/session-context-cursor.test.ts b/tests/helpers/session-context-cursor.test.ts index 92a1cf7a..a1aebfc2 100644 --- a/tests/helpers/session-context-cursor.test.ts +++ b/tests/helpers/session-context-cursor.test.ts @@ -13,7 +13,11 @@ describe("readCursorSession", () => { // homedir() caching on Linux doesn't break the tests. const uniqueId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const projectRoot = `/__archgate_cursor_test_${uniqueId}`; - const encodedProject = projectRoot.replaceAll("/", "-"); + const encodedProject = projectRoot + .replaceAll("/", "-") + .replaceAll("\\", "-") + .replaceAll(":", "-") + .replaceAll(".", "-"); let transcriptsDir: string; beforeEach(() => { diff --git a/tests/helpers/session-context.test.ts b/tests/helpers/session-context.test.ts index 735ea509..5da6662a 100644 --- a/tests/helpers/session-context.test.ts +++ b/tests/helpers/session-context.test.ts @@ -30,17 +30,31 @@ describe("encodeProjectPath", () => { expect(await encodeProjectPath("/a//b")).toBe("-a--b"); }); - test("replaces backslashes with dashes (Windows paths)", async () => { + test("replaces backslashes and colons with dashes (Windows paths)", async () => { expect(await encodeProjectPath("C:\\Users\\user\\project")).toBe( - "C:-Users-user-project" + "C--Users-user-project" ); }); test("handles mixed slashes", async () => { expect(await encodeProjectPath("C:\\Users/user\\project")).toBe( - "C:-Users-user-project" + "C--Users-user-project" ); }); + + test("replaces dots with dashes", async () => { + expect(await encodeProjectPath("/home/user/.config/project")).toBe( + "-home-user--config-project" + ); + }); + + test("encodes Windows worktree path (colons, backslashes, dots)", async () => { + expect( + await encodeProjectPath( + "E:\\archgate\\cli\\.claude\\worktrees\\fancy-prancing-sedgewick" + ) + ).toBe("E--archgate-cli--claude-worktrees-fancy-prancing-sedgewick"); + }); }); describe("readClaudeCodeSession", () => { @@ -62,7 +76,11 @@ describe("readClaudeCodeSession", () => { // homedir() caching on Linux doesn't break the tests. const uniqueId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const projectRoot = `/__archgate_test_${uniqueId}`; - const encodedProject = projectRoot.replaceAll("/", "-"); + const encodedProject = projectRoot + .replaceAll("/", "-") + .replaceAll("\\", "-") + .replaceAll(":", "-") + .replaceAll(".", "-"); let projectsDir: string; beforeEach(() => {