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.
This commit is contained in:
LukeParkerDev 2026-04-08 15:14:45 +10:00
parent f1547de528
commit ee0884ad31
3 changed files with 44 additions and 10 deletions

View file

@ -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)",

View file

@ -461,15 +461,11 @@ export namespace Config {
if (typeof x === "string") return { "*": x as PermissionAction }
const obj = x as { __originalKeys?: string[] } & Record<string, unknown>
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<string, PermissionRule>
const result: Record<string, PermissionRule> = {}
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
}

View file

@ -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<string, string>
expect(shell?.["rm -rf *"]).toBe("deny")
expect(shell?.["curl *"]).toBe("deny")
const bash = config.permission?.bash as Record<string, string>
expect(bash?.["rm -rf *"]).toBe("deny")
expect(bash?.["curl *"]).toBe("deny")
})
test("parseManagedPlist parses enabled_providers", async () => {