From 648d7138fc2d78b29d9e90d4b240e73a523c88f3 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Wed, 11 Feb 2026 01:43:45 -0800 Subject: [PATCH] fix: normalize path separators in isMemoryDirCommand for Windows (#914) Co-authored-by: Letta --- src/permissions/readOnlyShell.ts | 30 +++++++++++++++------ src/tests/permissions/readOnlyShell.test.ts | 3 ++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/permissions/readOnlyShell.ts b/src/permissions/readOnlyShell.ts index f086160..1f11f39 100644 --- a/src/permissions/readOnlyShell.ts +++ b/src/permissions/readOnlyShell.ts @@ -289,34 +289,48 @@ function extractDashCArgument(tokens: string[]): string | undefined { function getAllowedMemoryPrefixes(agentId: string): string[] { const home = homedir(); const prefixes: string[] = [ - resolve(home, ".letta", "agents", agentId, "memory"), - resolve(home, ".letta", "agents", agentId, "memory-worktrees"), + normalizeSeparators(resolve(home, ".letta", "agents", agentId, "memory")), + normalizeSeparators( + resolve(home, ".letta", "agents", agentId, "memory-worktrees"), + ), ]; const parentId = process.env.LETTA_PARENT_AGENT_ID; if (parentId && parentId !== agentId) { prefixes.push( - resolve(home, ".letta", "agents", parentId, "memory"), - resolve(home, ".letta", "agents", parentId, "memory-worktrees"), + normalizeSeparators( + resolve(home, ".letta", "agents", parentId, "memory"), + ), + normalizeSeparators( + resolve(home, ".letta", "agents", parentId, "memory-worktrees"), + ), ); } return prefixes; } +/** + * Normalize a path to forward slashes (for consistent comparison on Windows). + */ +function normalizeSeparators(p: string): string { + return p.replace(/\\/g, "/"); +} + /** * Resolve a path that may contain ~ or $HOME to an absolute path. + * Always returns forward slashes for cross-platform consistency. */ function expandPath(p: string): string { const home = homedir(); if (p.startsWith("~/")) { - return resolve(home, p.slice(2)); + return normalizeSeparators(resolve(home, p.slice(2))); } if (p.startsWith("$HOME/")) { - return resolve(home, p.slice(6)); + return normalizeSeparators(resolve(home, p.slice(6))); } if (p.startsWith('"$HOME/')) { - return resolve(home, p.slice(7).replace(/"$/, "")); + return normalizeSeparators(resolve(home, p.slice(7).replace(/"$/, ""))); } - return resolve(p); + return normalizeSeparators(resolve(p)); } /** diff --git a/src/tests/permissions/readOnlyShell.test.ts b/src/tests/permissions/readOnlyShell.test.ts index 7292007..fb31091 100644 --- a/src/tests/permissions/readOnlyShell.test.ts +++ b/src/tests/permissions/readOnlyShell.test.ts @@ -248,7 +248,8 @@ describe("isReadOnlyShellCommand", () => { describe("isMemoryDirCommand", () => { const AGENT_ID = "agent-test-abc123"; - const home = homedir(); + // Normalize to forward slashes for shell command strings (even on Windows) + const home = homedir().replace(/\\/g, "/"); const memDir = `${home}/.letta/agents/${AGENT_ID}/memory`; const worktreeDir = `${home}/.letta/agents/${AGENT_ID}/memory-worktrees`;