From d5322f9467d77c2e2042279eaf83f67edcbb0796 Mon Sep 17 00:00:00 2001 From: Daniel Olowoniyi Date: Wed, 21 Jan 2026 11:15:52 +0100 Subject: [PATCH 1/4] feat: implement home directory expansion for permission patterns using `~` and `$HOME` prefixes. --- packages/opencode/src/permission/next.ts | 12 ++++++- .../opencode/test/permission/next.test.ts | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index f95aaf34525..fc3f01b7fa0 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -7,11 +7,19 @@ import { Storage } from "@/storage/storage" import { fn } from "@/util/fn" import { Log } from "@/util/log" import { Wildcard } from "@/util/wildcard" +import os from "os" import z from "zod" export namespace PermissionNext { const log = Log.create({ service: "permission" }) + function expand(pattern: string): string { + if (pattern.startsWith("~/")) return os.homedir() + pattern.slice(1) + if (pattern.startsWith("$HOME/")) return os.homedir() + pattern.slice(5) + if (pattern.startsWith("$HOME")) return os.homedir() + pattern.slice(5) + return pattern + } + export const Action = z.enum(["allow", "deny", "ask"]).meta({ ref: "PermissionAction", }) @@ -44,7 +52,9 @@ export namespace PermissionNext { }) continue } - ruleset.push(...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern, action }))) + ruleset.push( + ...Object.entries(value).map(([pattern, action]) => ({ permission: key, pattern: expand(pattern), action })), + ) } return ruleset } diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 68dc653de6d..70319e197b4 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -1,4 +1,5 @@ import { test, expect } from "bun:test" +import os from "os" import { PermissionNext } from "../../src/permission/next" import { Instance } from "../../src/project/instance" import { Storage } from "../../src/storage/storage" @@ -38,6 +39,38 @@ test("fromConfig - empty object", () => { expect(result).toEqual([]) }) +test("fromConfig - expands tilde to home directory", () => { + const result = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: `${os.homedir()}/projects/*`, action: "allow" }]) +}) + +test("fromConfig - expands $HOME to home directory", () => { + const result = PermissionNext.fromConfig({ external_directory: { "$HOME/projects/*": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: `${os.homedir()}/projects/*`, action: "allow" }]) +}) + +test("fromConfig - expands $HOME without trailing slash", () => { + const result = PermissionNext.fromConfig({ external_directory: { $HOME: "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }]) +}) + +test("fromConfig - does not expand tilde in middle of path", () => { + const result = PermissionNext.fromConfig({ external_directory: { "/some/~/path": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }]) +}) + +test("evaluate - matches expanded tilde pattern", () => { + const ruleset = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } }) + const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset) + expect(result.action).toBe("allow") +}) + +test("evaluate - matches expanded $HOME pattern", () => { + const ruleset = PermissionNext.fromConfig({ external_directory: { "$HOME/projects/*": "allow" } }) + const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset) + expect(result.action).toBe("allow") +}) + // merge tests test("merge - simple concatenation", () => { From 21d068450ec55178d4bf0c43b27e8dcbf8b5d578 Mon Sep 17 00:00:00 2001 From: Daniel Olowoniyi Date: Wed, 21 Jan 2026 19:31:46 +0100 Subject: [PATCH 2/4] feat: add home directory expansion section to permissions documentation --- packages/web/src/content/docs/permissions.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/web/src/content/docs/permissions.mdx b/packages/web/src/content/docs/permissions.mdx index 4df3841e34a..99e5ca897a2 100644 --- a/packages/web/src/content/docs/permissions.mdx +++ b/packages/web/src/content/docs/permissions.mdx @@ -78,6 +78,13 @@ Permission patterns use simple wildcard matching: - `?` matches exactly one character - All other characters match literally +### Home Directory Expansion + +You can use `~` or `$HOME` at the start of a pattern to reference your home directory. This is particularly useful for `external_directory` rules. + +- `~/projects/*` -> `/Users/username/projects/*` +- `$HOME/projects/*` -> `/Users/username/projects/*` + --- ## Available Permissions From d143b682b1db1d6fe891bb07ac02361b56856857 Mon Sep 17 00:00:00 2001 From: Daniel Olowoniyi Date: Wed, 21 Jan 2026 19:38:03 +0100 Subject: [PATCH 3/4] feat: enhance home directory expansion to handle exact tilde in permission patterns --- packages/opencode/src/permission/next.ts | 1 + packages/opencode/test/permission/next.test.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index fc3f01b7fa0..2481f104ed1 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -15,6 +15,7 @@ export namespace PermissionNext { function expand(pattern: string): string { if (pattern.startsWith("~/")) return os.homedir() + pattern.slice(1) + if (pattern === "~") return os.homedir() if (pattern.startsWith("$HOME/")) return os.homedir() + pattern.slice(5) if (pattern.startsWith("$HOME")) return os.homedir() + pattern.slice(5) return pattern diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 70319e197b4..29f1efa4019 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -59,6 +59,11 @@ test("fromConfig - does not expand tilde in middle of path", () => { expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }]) }) +test("fromConfig - expands exact tilde to home directory", () => { + const result = PermissionNext.fromConfig({ external_directory: { "~": "allow" } }) + expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }]) +}) + test("evaluate - matches expanded tilde pattern", () => { const ruleset = PermissionNext.fromConfig({ external_directory: { "~/projects/*": "allow" } }) const result = PermissionNext.evaluate("external_directory", `${os.homedir()}/projects/file.txt`, ruleset) From f2251fded805a0265d94367bff7a2233ad0e1194 Mon Sep 17 00:00:00 2001 From: Daniel Olowoniyi Date: Wed, 21 Jan 2026 19:44:37 +0100 Subject: [PATCH 4/4] feat: clarify home directory expansion examples in permissions documentation --- packages/web/src/content/docs/permissions.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/content/docs/permissions.mdx b/packages/web/src/content/docs/permissions.mdx index 99e5ca897a2..229cb264e64 100644 --- a/packages/web/src/content/docs/permissions.mdx +++ b/packages/web/src/content/docs/permissions.mdx @@ -84,6 +84,7 @@ You can use `~` or `$HOME` at the start of a pattern to reference your home dire - `~/projects/*` -> `/Users/username/projects/*` - `$HOME/projects/*` -> `/Users/username/projects/*` +- `~` -> `/Users/username` ---