permission.ask plugin hook is defined but not triggered #4269

Open
opened 2026-02-16 17:43:14 -05:00 by yindo · 2 comments
Owner

Originally created by @markerikson on GitHub (Jan 5, 2026).

Originally assigned to: @rekram1-node on GitHub.

Description

Hi! First time filing an issue with OpenCode. Brand new user within the last few days. Tried using the new Permissions system from https://github.com/anomalyco/opencode/pull/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 TestPermissionHook

Suggested 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 #6319)
  • The hook type exists in @opencode-ai/plugin but implementation is missing

Plugins

CodeNomad; custom permissions auto-approve plugin

OpenCode version

1.1.2

Steps to reproduce

  1. Add the listed repro permissions plugin to OpenCode
  2. Run a command with date
  3. 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)

Originally created by @markerikson on GitHub (Jan 5, 2026). Originally assigned to: @rekram1-node on GitHub. ### Description Hi! First time filing an issue with OpenCode. Brand new user within the last few days. Tried using the new Permissions system from https://github.com/anomalyco/opencode/pull/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: ```typescript 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: ```typescript 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: ```typescript 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: ```ts // ~/.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 TestPermissionHook ``` ### Suggested Fix ```diff 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 #6319) - The hook type exists in `@opencode-ai/plugin` but implementation is missing ### Plugins CodeNomad; custom permissions auto-approve plugin ### OpenCode version 1.1.2 ### Steps to reproduce 1. Add the listed repro permissions plugin to OpenCode 2. Run a command with `date` 3. 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)
yindo added the bug label 2026-02-16 17:43:14 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Jan 5, 2026):

This issue might be a duplicate of or related to existing issues. Please check:

  • #6892: MCP tool permissions not working after migration from tools to permissions - Related to the permissions system overhaul mentioned in #7006
  • #6856: Should we actually implement the new granular permissions in .md agent files? - Discusses the incomplete permissions system implementation
  • #6043: Add askPermission to plugin ToolContext - Related feature request for plugin permission handling
  • #6862: Tool use cannot be prevented early in a session - Related issue with plugin hooks not firing at the right time
  • #6527: Plan mode restrictions bypassed when spawning sub-agents - Security issue related to permissions not being properly enforced

The core issue appears to be that while the permission system was redesigned (as mentioned in PR #6319), the plugin hook integration for "permission.ask" was not fully implemented, preventing plugins from intercepting and auto-approving/denying permission requests.

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Jan 5, 2026): This issue might be a duplicate of or related to existing issues. Please check: - #6892: MCP tool permissions not working after migration from tools to permissions - Related to the permissions system overhaul mentioned in #7006 - #6856: Should we actually implement the new granular permissions in .md agent files? - Discusses the incomplete permissions system implementation - #6043: Add askPermission to plugin ToolContext - Related feature request for plugin permission handling - #6862: Tool use cannot be prevented early in a session - Related issue with plugin hooks not firing at the right time - #6527: Plan mode restrictions bypassed when spawning sub-agents - Security issue related to permissions not being properly enforced The core issue appears to be that while the permission system was redesigned (as mentioned in PR #6319), the plugin hook integration for "permission.ask" was not fully implemented, preventing plugins from intercepting and auto-approving/denying permission requests. Feel free to ignore if none of these address your specific case.
Author
Owner

@select commented on GitHub (Jan 17, 2026):

Thanks for clarifying this is a bug, the docs do not list this permission (yet) and I was hoping to use it to trigger a tts output.

@select commented on GitHub (Jan 17, 2026): Thanks for clarifying this is a bug, the docs do not list this permission (yet) and I was hoping to use it to trigger a tts output.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#4269