From ee0884ad313786ee97969bd42178ac66df394a81 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:14:45 +1000 Subject: [PATCH] fix(shell): preserve legacy bash compatibility Keep mixed shell/bash permission configs ordered correctly and treat --tools bash as the legacy alias during agent creation. --- packages/opencode/src/cli/cmd/agent.ts | 12 ++++++- packages/opencode/src/config/config.ts | 8 ++--- packages/opencode/test/config/config.test.ts | 34 ++++++++++++++++++-- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index eda4175b9f..2b8999bb59 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -11,6 +11,7 @@ import matter from "gray-matter" import { Instance } from "../../project/instance" import { EOL } from "os" import type { Argv } from "yargs" +import { ShellToolID } from "../../tool/shell/id" type AgentMode = "all" | "primary" | "subagent" @@ -120,7 +121,16 @@ const AgentCreateCommand = cmd({ // Select tools let selectedTools: string[] if (cliTools !== undefined) { - selectedTools = cliTools ? cliTools.split(",").map((t) => t.trim()) : AVAILABLE_TOOLS + selectedTools = cliTools + ? [ + ...new Set( + cliTools + .split(",") + .map((t) => ShellToolID.normalize(t.trim())) + .filter(Boolean), + ), + ] + : AVAILABLE_TOOLS } else { const result = await prompts.multiselect({ message: "Select tools to enable (Space to toggle)", diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index e47cbfc7c3..27e6ebe547 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -461,15 +461,11 @@ export namespace Config { if (typeof x === "string") return { "*": x as PermissionAction } const obj = x as { __originalKeys?: string[] } & Record const { __originalKeys, ...rest } = obj - if (!__originalKeys) { - return Object.fromEntries( - Object.entries(rest).map(([key, value]) => [ShellToolID.normalize(key), value as PermissionRule]), - ) - } + if (!__originalKeys) return rest as Record const result: Record = {} for (const key of __originalKeys) { if (!(key in rest)) continue - result[ShellToolID.normalize(key)] = rest[key] as PermissionRule + result[key] = rest[key] as PermissionRule } return result } diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index fe7cf31913..302374e4e2 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1609,6 +1609,34 @@ test("permission config preserves key order", async () => { }) }) +test("permission config preserves shell and legacy bash order", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Filesystem.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + permission: { + shell: "deny", + bash: "allow", + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(Object.keys(config.permission!)).toEqual(["shell", "bash"]) + expect(config.permission).toEqual({ + shell: "deny", + bash: "allow", + }) + }, + }) +}) + // MCP config merging tests test("project config can override MCP server enabled status", async () => { @@ -2339,9 +2367,9 @@ test("parseManagedPlist parses permission rules", async () => { expect(config.permission?.grep).toBe("allow") expect(config.permission?.webfetch).toBe("ask") expect(config.permission?.["~/.ssh/*"]).toBe("deny") - const shell = config.permission?.shell as Record - expect(shell?.["rm -rf *"]).toBe("deny") - expect(shell?.["curl *"]).toBe("deny") + const bash = config.permission?.bash as Record + expect(bash?.["rm -rf *"]).toBe("deny") + expect(bash?.["curl *"]).toBe("deny") }) test("parseManagedPlist parses enabled_providers", async () => {