From 32ec3666b7ec18d644a6a8f720a75056bf27daa1 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:08:30 +1000 Subject: [PATCH] fix(shell): keep shell config consistent Treat shell access as one logical toggle during agent creation and apply bash compatibility rules before explicit per-shell overrides. This avoids disabling the active Windows shell unexpectedly and keeps pwsh and powershell overrides deterministic. --- packages/opencode/src/cli/cmd/agent.ts | 14 +---- packages/opencode/src/permission/index.ts | 58 +++++++------------ .../opencode/test/permission/next.test.ts | 24 ++++++++ 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index d0777d3ef7..70082c8e2e 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -9,24 +9,12 @@ import fs from "fs/promises" import { Filesystem } from "../../util/filesystem" import matter from "gray-matter" import { Instance } from "../../project/instance" -import { ShellTool } from "../../tool/shell/id" import { EOL } from "os" import type { Argv } from "yargs" type AgentMode = "all" | "primary" | "subagent" -const AVAILABLE_TOOLS = [ - ...ShellTool.ids, - "read", - "write", - "edit", - "list", - "glob", - "grep", - "webfetch", - "task", - "todowrite", -] +const AVAILABLE_TOOLS = ["bash", "read", "write", "edit", "list", "glob", "grep", "webfetch", "task", "todowrite"] const AgentCreateCommand = cmd({ command: "create", diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 638d5ebe12..1ddd9cec32 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -276,46 +276,30 @@ export namespace Permission { return pattern } + function pushRules(ruleset: Ruleset, permission: string, value: Config.PermissionRule) { + if (typeof value === "string") { + ruleset.push({ permission, action: value, pattern: "*" }) + return + } + + ruleset.push( + ...Object.entries(value).map(([pattern, action]) => ({ permission, pattern: expand(pattern), action })), + ) + } + export function fromConfig(permission: Config.Permission) { const ruleset: Ruleset = [] + + const bash = permission["bash"] + if (bash !== undefined) { + pushRules(ruleset, "bash", bash) + pushRules(ruleset, "pwsh", bash) + pushRules(ruleset, "powershell", bash) + } + for (const [key, value] of Object.entries(permission)) { - if (key === "bash") { - if (typeof value === "string") { - ruleset.push({ permission: "bash", action: value, pattern: "*" }) - ruleset.push({ permission: "pwsh", action: value, pattern: "*" }) - ruleset.push({ permission: "powershell", action: value, pattern: "*" }) - } else { - ruleset.push( - ...Object.entries(value).map(([pattern, action]) => ({ - permission: "bash", - pattern: expand(pattern), - action, - })), - ) - ruleset.push( - ...Object.entries(value).map(([pattern, action]) => ({ - permission: "pwsh", - pattern: expand(pattern), - action, - })), - ) - ruleset.push( - ...Object.entries(value).map(([pattern, action]) => ({ - permission: "powershell", - pattern: expand(pattern), - action, - })), - ) - } - continue - } - if (typeof value === "string") { - ruleset.push({ permission: key, action: value, pattern: "*" }) - continue - } - ruleset.push( - ...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern: expand(pattern), action })), - ) + if (key === "bash") continue + pushRules(ruleset, key, value) } return ruleset } diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 7e48fe33ff..b98f4a3ae0 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -71,6 +71,30 @@ test("fromConfig - mixed string and object values", () => { ]) }) +test("fromConfig - explicit pwsh overrides bash regardless of key order", () => { + const result = Permission.fromConfig({ + pwsh: "deny", + bash: "allow", + }) + expect(result).toEqual([ + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "pwsh", pattern: "*", action: "allow" }, + { permission: "powershell", pattern: "*", action: "allow" }, + { permission: "pwsh", pattern: "*", action: "deny" }, + ]) + expect(Permission.evaluate("pwsh", "ls", result).action).toBe("deny") + expect(Permission.evaluate("bash", "ls", result).action).toBe("allow") +}) + +test("fromConfig - explicit powershell pattern overrides bash pattern regardless of key order", () => { + const result = Permission.fromConfig({ + powershell: { "rm *": "deny" }, + bash: { "*": "allow", "rm *": "ask" }, + }) + expect(Permission.evaluate("powershell", "rm foo", result).action).toBe("deny") + expect(Permission.evaluate("pwsh", "rm foo", result).action).toBe("ask") +}) + test("fromConfig - empty object", () => { const result = Permission.fromConfig({}) expect(result).toEqual([])