From e7ff7143b6d13b2204ce0a0c58c428362a6a83fa Mon Sep 17 00:00:00 2001
From: Patrick Schiel
Date: Fri, 30 Jan 2026 23:11:45 +0100
Subject: [PATCH] fix: handle redirected_statement treesitter node in bash
permissions (#6737)
---
packages/opencode/src/tool/bash.ts | 8 +++--
packages/opencode/test/tool/bash.test.ts | 43 ++++++++++++++++++++++++
2 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts
index bf7c524941..5d073fd681 100644
--- a/packages/opencode/src/tool/bash.ts
+++ b/packages/opencode/src/tool/bash.ts
@@ -91,6 +91,10 @@ export const BashTool = Tool.define("bash", async () => {
for (const node of tree.rootNode.descendantsOfType("command")) {
if (!node) continue
+
+ // Get full command text including redirects if present
+ let commandText = node.parent?.type === "redirected_statement" ? node.parent.text : node.text
+
const command = []
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i)
@@ -131,8 +135,8 @@ export const BashTool = Tool.define("bash", async () => {
// cd covered by above check
if (command.length && command[0] !== "cd") {
- patterns.add(command.join(" "))
- always.add(BashArity.prefix(command).join(" ") + "*")
+ patterns.add(commandText)
+ always.add(BashArity.prefix(command).join(" ") + " *")
}
}
diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts
index 454293c8fb..ff12c4368f 100644
--- a/packages/opencode/test/tool/bash.test.ts
+++ b/packages/opencode/test/tool/bash.test.ts
@@ -231,6 +231,49 @@ describe("tool.bash permissions", () => {
},
})
})
+
+ test("matches redirects in permission pattern", async () => {
+ await using tmp = await tmpdir({ git: true })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const bash = await BashTool.init()
+ const requests: Array> = []
+ const testCtx = {
+ ...ctx,
+ ask: async (req: Omit) => {
+ requests.push(req)
+ },
+ }
+ await bash.execute({ command: "cat > /tmp/output.txt", description: "Redirect ls output" }, testCtx)
+ const bashReq = requests.find((r) => r.permission === "bash")
+ expect(bashReq).toBeDefined()
+ expect(bashReq!.patterns).toContain("cat > /tmp/output.txt")
+ },
+ })
+ })
+
+ test("always pattern has space before wildcard to not include different commands", async () => {
+ await using tmp = await tmpdir({ git: true })
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const bash = await BashTool.init()
+ const requests: Array> = []
+ const testCtx = {
+ ...ctx,
+ ask: async (req: Omit) => {
+ requests.push(req)
+ },
+ }
+ await bash.execute({ command: "ls -la", description: "List" }, testCtx)
+ const bashReq = requests.find((r) => r.permission === "bash")
+ expect(bashReq).toBeDefined()
+ const pattern = bashReq!.always[0]
+ expect(pattern).toBe("ls *")
+ },
+ })
+ })
})
describe("tool.bash truncation", () => {