From 338610af41cde4b4ce7b90ee9b2681143724a889 Mon Sep 17 00:00:00 2001 From: Anas Date: Tue, 6 Jan 2026 12:17:09 +0000 Subject: [PATCH 1/2] fix(permission): trigger permission.ask plugin hook in PermissionNext The permission.ask hook was defined in the plugin SDK types but never triggered in PermissionNext.ask(), preventing plugins from intercepting permission requests. This fix adds the Plugin.trigger() call before showing the UI prompt, allowing plugins like oh-my-opencode to auto-approve or auto-deny based on custom logic (e.g., PreToolUse hooks for delegation enforcement). Fixes #7006 --- packages/opencode/src/permission/next.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index 9a0395fa1ec..f43c3b497d8 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -3,6 +3,7 @@ import { BusEvent } from "@/bus/bus-event" import { Config } from "@/config/config" import { Identifier } from "@/id/id" import { Instance } from "@/project/instance" +import { Plugin } from "@/plugin" import { Storage } from "@/storage/storage" import { fn } from "@/util/fn" import { Log } from "@/util/log" @@ -127,11 +128,24 @@ export namespace PermissionNext { throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) if (rule.action === "ask") { const id = input.id ?? Identifier.ascending("permission") + const info: Request = { + id, + ...request, + } + + // Allow plugins to intercept permission requests (fixes #7006) + const hookStatus = await Plugin.trigger("permission.ask", info, { status: "ask" as const }).then((x) => x.status) + if (hookStatus === "allow") { + log.info("plugin auto-approved", { permission: request.permission, pattern }) + continue // Auto-approve + } + if (hookStatus === "deny") { + log.info("plugin auto-denied", { permission: request.permission, pattern }) + throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) + } + + // If hook returns "ask" or nothing, proceed with UI prompt return new Promise((resolve, reject) => { - const info: Request = { - id, - ...request, - } s.pending[id] = { info, resolve, From df1fd293545fb417a0bc29f581ac89cfd5f568f1 Mon Sep 17 00:00:00 2001 From: Anas Date: Tue, 6 Jan 2026 12:19:03 +0000 Subject: [PATCH 2/2] fix: use switch statement for type safety --- packages/opencode/src/permission/next.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts index f43c3b497d8..fa89122d516 100644 --- a/packages/opencode/src/permission/next.ts +++ b/packages/opencode/src/permission/next.ts @@ -134,17 +134,20 @@ export namespace PermissionNext { } // Allow plugins to intercept permission requests (fixes #7006) - const hookStatus = await Plugin.trigger("permission.ask", info, { status: "ask" as const }).then((x) => x.status) - if (hookStatus === "allow") { - log.info("plugin auto-approved", { permission: request.permission, pattern }) - continue // Auto-approve - } - if (hookStatus === "deny") { - log.info("plugin auto-denied", { permission: request.permission, pattern }) - throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) + switch ( + await Plugin.trigger("permission.ask", info, { + status: "ask", + }).then((x) => x.status) + ) { + case "allow": + log.info("plugin auto-approved", { permission: request.permission, pattern }) + continue // Auto-approve + case "deny": + log.info("plugin auto-denied", { permission: request.permission, pattern }) + throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission))) } - // If hook returns "ask" or nothing, proceed with UI prompt + // If hook returns "ask" or unchanged, proceed with UI prompt return new Promise((resolve, reject) => { s.pending[id] = { info,