-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Description
Description
Hi! First time filing an issue with OpenCode. Brand new user within the last few days. Tried using the new Permissions system from #6319 , tried to use it in a Plugin to customize behavior with some auto-approvals. Opus wrote the plugin for me, but it doesn't seem to be doing anything. It's being detected, loaded, and initialized, but not actually executing.
Here's the Opus-generated issue description and suggested fix:
Description
The permission.ask hook is defined in the plugin SDK types (@opencode-ai/plugin) but is never actually triggered by the permission system. This prevents plugins from intercepting permission requests and programmatically allowing/denying them.
Current Behavior
The PermissionNext.ask() function in packages/opencode/src/permission/next.ts evaluates rules and, if the result is "ask", immediately publishes a bus event for the UI:
if (rule.action === "ask") {
const id = input.id ?? Identifier.ascending("permission")
return new Promise<void>((resolve, reject) => {
const info: Request = {
id,
...request,
}
s.pending[id] = {
info,
resolve,
reject,
}
Bus.publish(Event.Asked, info) // Goes straight to UI
})
}The Plugin.trigger("permission.ask", ...) is never called.
Expected Behavior
Before prompting the user, the system should call the permission.ask plugin hook, allowing plugins to override the decision:
if (rule.action === "ask") {
const id = input.id ?? Identifier.ascending("permission")
const info: Request = {
id,
...request,
}
// Let plugins intercept
const hookOutput = await Plugin.trigger(
"permission.ask",
info,
{ status: "ask" as "ask" | "allow" | "deny" }
)
if (hookOutput.status === "allow") {
return // Auto-approved by plugin
}
if (hookOutput.status === "deny") {
throw new DeniedError([]) // Or a new PluginDeniedError
}
// Still "ask" - prompt the user
return new Promise<void>((resolve, reject) => {
s.pending[id] = {
info,
resolve,
reject,
}
Bus.publish(Event.Asked, info)
})
}Use Case
This would enable plugins like:
export const AutoApprovePlugin: Plugin = async (ctx) => {
return {
"permission.ask": async (input, output) => {
// Auto-approve bash commands matching safe patterns
if (input.permission === "bash") {
const cmd = input.metadata?.command ?? input.patterns[0]
if (isSafeCommand(cmd)) {
output.status = "allow"
}
}
// Auto-approve file ops in specific directories
if (["read", "edit"].includes(input.permission)) {
if (input.patterns.every(p => p.startsWith("/safe/path/"))) {
output.status = "allow"
}
}
},
}
}This is similar to Claude Code's PreToolUse hook pattern, which allows compositional command analysis (e.g., timeout 30 git status → strip wrapper → check core command).
Repro
Here's a tiny permissions plugin that demonstrates the intended usage:
// ~/.config/opencode/plugin/test-permission-hook.ts
import type { Plugin } from "@opencode-ai/plugin"
import { appendFileSync } from "fs"
const LOG = "/tmp/permission-hook-test.log"
function log(msg: string) {
appendFileSync(LOG, `[${new Date().toISOString()}] ${msg}\n`)
}
const TestPermissionHook: Plugin = async () => {
log("Plugin loaded")
return {
"permission.ask": async (input, output) => {
log(`permission.ask called: ${input.permission}`)
log(` patterns: ${JSON.stringify(input.patterns)}`)
log(` metadata: ${JSON.stringify(input.metadata)}`)
// Auto-approve any 'date' command as a test
if (input.permission === "bash" && input.patterns.some(p => p.includes("date"))) {
log(` -> Setting status to "allow"`)
output.status = "allow"
}
},
}
}
export default TestPermissionHookSuggested Fix
diff --git a/packages/opencode/src/permission/next.ts b/packages/opencode/src/permission/next.ts
index abc123..def456 100644
--- a/packages/opencode/src/permission/next.ts
+++ b/packages/opencode/src/permission/next.ts
@@ -5,6 +5,7 @@ import { Identifier } from "@/id/id"
import { Instance } from "@/project/instance"
import { Storage } from "@/storage/storage"
import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { Wildcard } from "@/util/wildcard"
+import { Plugin } from "@/plugin"
import z from "zod"
export namespace PermissionNext {
@@ -97,15 +98,27 @@ export namespace PermissionNext {
if (rule.action === "deny")
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,
+ }
+
+ // Let plugins intercept the permission request
+ const hookResult = await Plugin.trigger(
+ "permission.ask",
+ info,
+ { status: "ask" as Action }
+ )
+
+ if (hookResult.status === "allow") {
+ log.info("plugin auto-approved", { permission: request.permission, pattern })
+ return
+ }
+ if (hookResult.status === "deny") {
+ log.info("plugin auto-denied", { permission: request.permission, pattern })
+ throw new DeniedError([{ permission: request.permission, pattern, action: "deny" }])
+ }
+
return new Promise<void>((resolve, reject) => {
- const info: Request = {
- id,
- ...request,
- }
s.pending[id] = {
info,
resolve,
reject,
}
Bus.publish(Event.Asked, info)
})
}
if (rule.action === "allow") continue
}
},
)Environment
- OpenCode version: 1.1.x (post permissions PR Permission rework #6319)
- The hook type exists in
@opencode-ai/pluginbut implementation is missing
Plugins
CodeNomad; custom permissions auto-approve plugin
OpenCode version
1.1.2
Steps to reproduce
- Add the listed repro permissions plugin to OpenCode
- Run a command with
date - See that the command was not auto-approved
Screenshot and/or share link
No response
Operating System
Windows 11, WSL Ubuntu
Terminal
CodeNomad UI (server in WSL, browser UI in Windows Chrome)