From 1a2542b11be9d9c6bb8bcce534bcf8561bc4542e Mon Sep 17 00:00:00 2001 From: Aaron Ky-Riesenbach Date: Thu, 9 Jul 2026 14:23:06 -0700 Subject: [PATCH] feat(coding-agent): expand ~ in shellPath setting (#6470) getShellPath() now runs the configured shellPath through the existing normalizePath() helper, mirroring getSessionDir(). This lets shellPath point at a home-directory-relative shell wrapper (e.g. ~/.local/bin/agent-shell-sandbox) and resolves consistently across machines and OSes with different home directories. refs #6458 --- packages/coding-agent/docs/settings.md | 2 +- .../coding-agent/src/core/settings-manager.ts | 5 ++-- .../test/settings-manager.test.ts | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/docs/settings.md b/packages/coding-agent/docs/settings.md index bcbd2f1a..72337631 100644 --- a/packages/coding-agent/docs/settings.md +++ b/packages/coding-agent/docs/settings.md @@ -185,7 +185,7 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic | Setting | Type | Default | Description | |---------|------|---------|-------------| -| `shellPath` | string | - | Custom shell path (e.g., for Cygwin on Windows) | +| `shellPath` | string | - | Custom shell path (e.g., for Cygwin on Windows); supports a leading `~` for the home directory | | `shellCommandPrefix` | string | - | Prefix for every bash command (e.g., `"shopt -s expand_aliases"`) | | `npmCommand` | string[] | - | Command argv used for npm package lookup/install operations (e.g., `["mise", "exec", "node@20", "--", "npm"]`) | diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index c88403ec..e6cae1af 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -95,7 +95,7 @@ export interface Settings { hideThinkingBlock?: boolean; showCacheMissNotices?: boolean; // default: false - show transcript notices for significant prompt-cache misses externalEditor?: string; // Command for Ctrl+G external editor; takes precedence over VISUAL/EDITOR - shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows) + shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows); supports leading ~ expansion quietStartup?: boolean; defaultProjectTrust?: DefaultProjectTrust; // default: "ask"; global setting only shellCommandPrefix?: string; // Prefix prepended to every bash command (e.g., "shopt -s expand_aliases" for alias support) @@ -876,7 +876,8 @@ export class SettingsManager { } getShellPath(): string | undefined { - return this.settings.shellPath; + const shellPath = this.settings.shellPath; + return shellPath ? normalizePath(shellPath) : shellPath; } setShellPath(path: string | undefined): void { diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 2642b0d6..46387997 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -479,4 +479,33 @@ describe("SettingsManager", () => { expect(manager.getSessionDir()).toBe(join(homedir(), "sessions")); }); }); + + describe("getShellPath", () => { + it("should return undefined when not set", () => { + writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ theme: "dark" })); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getShellPath()).toBeUndefined(); + }); + + it("should return an absolute shellPath unchanged", () => { + writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ shellPath: "/bin/zsh" })); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getShellPath()).toBe("/bin/zsh"); + }); + + it("should expand ~ in shellPath", () => { + writeFileSync( + join(agentDir, "settings.json"), + JSON.stringify({ shellPath: "~/.local/bin/agent-shell-sandbox" }), + ); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getShellPath()).toBe(join(homedir(), ".local/bin/agent-shell-sandbox")); + }); + + it("should expand a bare ~ in shellPath", () => { + writeFileSync(join(agentDir, "settings.json"), JSON.stringify({ shellPath: "~" })); + const manager = SettingsManager.create(projectDir, agentDir); + expect(manager.getShellPath()).toBe(homedir()); + }); + }); });