Skip to content

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

Closed

Description

@markerikson

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 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 Permission rework #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)

Activity

  1. github-actions commented on Jan 5, 2026

    @github-actions
    Contributor

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

    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.

  2. added a commit that references this issue on Jan 6, 2026
    338610a
  3. select commented on Jan 17, 2026

    @select

    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.

  4. ndrwstn commented on Mar 17, 2026

    @ndrwstn

    What's the status on this issue? I was just trying to see if I could wire up ntfy to confirm/deny permissions when I ran across this as a blocker?

  5. markerikson commented on Mar 17, 2026

    @markerikson
    Author

    No one seems to have done it. I have a local fork that implemented the missing pieces and it's been working fine for me. I can PR it, although I have no idea if it'll get merged.

  6. repomaa commented on Mar 19, 2026

    @repomaa

    There's a workaround:

    const myPlugin = async () => ({
      event: ({ event }) => {
        // the types are wrong here, "permission.asked" isn't listed there
        if (event.type === ("permission.asked" as "permission.updated")) {
          // do stuff
        }
      },
    })
  7. ndrwstn commented on Mar 24, 2026

    @ndrwstn

    @markerikson -- if you PR it, you can tag rekram1-node and ask for a review tagging this issue. The team has historically been very responsive, just super busy.

  8. nickdavies commented on Mar 30, 2026

    @nickdavies

    @ndrwstn I put up #19453. I believe it solves this issue correctly but would appreciate a review from someone who knows the code better than I do

  9. ndrwstn commented on Apr 1, 2026

    @ndrwstn

    Sadly, I'm just a lowly user with just enough knowledge to be dangerous.

  10. 3 remaining items

  11. yohi commented on Jun 3, 2026

    @yohi

    I've implemented the missing Plugin.trigger for this hook along with test coverage. I will open a PR for this.

  12. kevholmes commented on Jul 29, 2026

    @kevholmes

    There are now several implementations addressing this issue:

    I opened #39442 because the earlier implementations no longer apply cleanly to the current Effect/LayerNode architecture. It keeps static allow and deny rules authoritative, invokes plugins only when OpenCode would otherwise ask, falls back to the normal prompt if a plugin fails, and has been verified with a live allow/deny/throw round trip.

    Could a maintainer confirm which direction the project wants: restoring the existing permission.ask contract at the ask boundary, or adopting the broader Gate design from #34329? I’m happy to adjust #39442 or defer to another PR once that direction is clear.

  13. Ryan9438 commented on Aug 20, 2026

    @Ryan9438

    Just passing by — and to be honest, I'm pretty new here (only been around this community for about two months), so please forgive me if I say anything wrong.

    A few days ago, I wanted the agent to explain why it was accessing a file outside the project directory before asking the user for permission. While building that, I found the permission prompt in the TUI simply never fired — the permission.ask hook didn't trigger no matter what I tried. As a beginner, I actually spent quite a while debugging my own code, thinking I was doing something wrong, before I realized the problem might not be on my side.

    In the end I had to fall back to a system-prompt-based approach and packaged it as a tiny plugin: opencode-explained-right. It's just a simple workaround — I know the proper fix belongs here, and mine is nowhere near as good as the PRs you all have been working on.

    Searching for the root cause eventually led me to this issue. I just wanted to say thanks to everyone investigating and submitting PRs — I've learned a lot just from reading this thread. No pressure to the maintainers at all, and I'd be very happy to help test once a fix lands. 🙏

  14. fwa-wup commented on Sep 1, 2026

    @fwa-wup

    Status ping + two data points from a downstream plugin maintainer who depends on this hook:

    1. feat(permission): restore permission ask hook safely #42633 verified working on a real build (details in my review there): compiled pull/42633/head (dd25dface) with bun 1.3.14 → 94/94 local tests (PR CI has no test job), clean typecheck, and on the resulting standalone binary the hook finally fires in opencode run mode, awaited before the prompt/auto-reject path — stock 1.18.25 with the identical probe: never fires. main (df6aecd) still has zero permission.ask trigger call sites while packages/plugin/src/index.ts keeps shipping the hook type, so every plugin that registers it is silently dead. feat(permission): restore permission ask hook safely #42633 is mergeable and green; what it lacks is reviewer attention.

    2. The regression may be older than the 38e0dc9 story suggests for stable releases. Static analysis of the published npm bundles opencode-ai@1.2.26 and 1.2.27 (both pre-38e0dc9, Mar 2026) found zero occurrences of the "permission.ask" hook literal and a dispatched-hook set containing no permission.* hook at all — plus an empirical probe on 1.2.26: plugin loads, hook never fires. So either the call site only ever existed on dev/beta snapshot builds, or stable users lost it earlier than thought. Doesn't change the fix (same one-liner-of-a-call-site restoration + semantics in feat(permission): restore permission ask hook safely #42633), but it explains why "pin an old version" is not a workaround for anyone on stable.

    Please consider merging #42633 (or folding its tests into #19453's review thread) — the entire auto-approval plugin ecosystem for opencode is currently a no-op on every published build, despite the SDK advertising the hook.

  15. lgarceau768 commented on Sep 2, 2026

    @lgarceau768

    I filed #46809 as a duplicate of this before finding it — closed. Two details from it that I could not find anywhere in this thread, both small, one relevant to the PRs under review.

    1. The bundled plugin docs now advertise the hook. In January the note above was "the docs do not list this permission (yet)". On 1.18.26 they do: the plugin-authoring doc shipped inside the binary lists permission.ask under "Hook surface (mutate output in place; return void)", between shell.env and the experimental.* hooks — every one of which fires. So it is not only the SDK type advertising it now.

    2. Permission.patterns carries the command, not the config pattern that matched. opencode run prints it as permission requested: bash (git commit -m '...'). This matters for whichever restoration lands: a plugin that adds its own permission.bash patterns in its config hook — to make a specific command askable so it can answer — cannot then tell its own permission from one the user configured for their own reasons. Answering allow on a permission the person deliberately asked for takes away a decision they wanted.

    If the payload carried the matched pattern (or always) alongside the command, a handler could scope itself to its own rules. Not a blocker for #42633, just cheaper to decide now than after.

    Thanks for the workaround note — the permission.asked event plus the reply endpoint works and is what I should have found first.

  16. NoahBPeterson commented on Sep 6, 2026

    @NoahBPeterson

    @thdxr @rekram1-node

    #47654

    This is causing multiple people issues. Is there a fix incoming? :)

  17. fwa-wup commented on Sep 17, 2026

    @fwa-wup

    Status update from the same downstream plugin maintainer (two new data points, both measured today):

    1. feat(permission): restore permission ask hook safely #42633 was closed by the automated stale-PR cleanup on 2026-09-14 (>1 month old, <2 positive reactions) — it was never reviewed or merged. The verification I posted here and in my review there (build of dd25dface: probe-green, run-mode hook awaited before the prompt path) still stands: that fix worked. It just needs a reviewer, or a successor with one.

    2. A newer successor now exists and I could make it go green: fix(opencode): run the documented permission.ask plugin hook #47675 (opened 2026-09-06, fix(opencode): run the documented permission.ask plugin hook, base dev, mergeable). Built pull/47675/head (bda782e) today and ran the same isolated end-to-end harness:

      • the permission.ask hook fires in run mode, awaited before the prompt/auto-reject path;
      • full plugin matrix green: classifier-SAFE bash auto-allowed end-to-end, UNSAFE rm -rf stayed blocked, .env read stayed ask-blocked (MATRIX: ALL PASS);
      • 97/97 PR unit tests + typecheck clean locally (the PR CI has no test job).
        Details in my review on fix(opencode): run the documented permission.ask plugin hook #47675. The payload is the v2 PermissionRequest ({permission, patterns[]}, no type/pattern) — worth stating in the eventual release notes since the previously declared type was the legacy shape.
    3. Current stable is still dead: stock 1.18.31 (released 2026-09-14, latest today) with the identical probe — plugin loads, hook never fires. Consistent with the earlier finding that no published stable ever dispatched this hook.

    Practical ask: #47675 is small (6 files, +412/−7), tested locally at 97/97, and now has an independent build-and-run verification on record. A maintainer review would un-block every plugin that registers this hook (several issues: this one, #47654, #47674, and the closed #42633 line).

  18. Atmostone commented on Sep 17, 2026

    @Atmostone

    There is another PR for this — #47675, against dev. Two things from working on it that may be worth the thread's attention, both of which apply to the earlier attempts too.

    @athal7's bisect is right, and it can be pinned exactly. This is a regression, not an unimplemented feature. At f015154, packages/opencode/src/permission/index.ts:127:

    switch (
      await Plugin.trigger("permission.ask", info, {
        status: "ask",
      }).then((x) => x.status)

    At 38e0dc9 ("Move service state into InstanceState, flatten service facades", #18483, 21 Mar) that call is gone; the file's only remaining permission.ask match is the permission.asked event definition. The declaration in packages/plugin and the docs were left pointing at a call site that had stopped existing.

    The declared input type is also wrong, and that bites anyone who restores only the call. The hook declares the legacy SDK Permission shape (type, pattern, title, time), but what the permission service hands over is the v2 PermissionRequest (permission, patterns, always, tool) — three of nine fields overlap. @fwa-wup hit this on a build of #47675 and had to fix their own plugin for it. Restoring the trigger alone therefore leaves plugin authors reading undefined from every field they would branch on, which is a quiet fail-open on a security path.

    #47675 changes the declaration to the shape that is actually passed and drops the casts. It also keeps a configured deny settling before the hook, keeps the rules' decision when a hook returns an unrecognised status or throws, and hands the hook copies rather than the live request object.

    No claim that it is better than the earlier attempts — several were verified and simply never reviewed. But the provenance above applies to all of them, and may be the detail that makes this reviewable.

  19. fwa-wup commented on Sep 21, 2026

    @fwa-wup

    Downstream-plugin maintainer here (probe+matrix verification posted on #47675, 2026-09-17). Status update with a new fork in the road:

    • 1.x line (npm latest = 1.18.31): permission.ask still never fires (re-probed SILENT on 1.18.31 today-ish). Live fix candidate remains fix(opencode): run the documented permission.ask plugin hook #47675 (verified green by us: probe + full acceptance matrix on a local build; 97/97 PR tests). Mergeable, but mergeable_state=blocked on review, and the stale-PR cleanup bot will close it ~2026-10-06 unless reviewed — the same death feat(permission): restore permission ask hook safely #42633 died on 2026-09-14. We +1'd to keep the reaction floor.
    • v2 line (tags v2.0.0–v2.0.11, Sept 2026, beta): measured on the v2.0.11 tree — packages/opencode/ no longer exists; the 1.x permission.ask hook and its dispatch are gone. Permissions are a new Effect-based permission.evaluate plugin hook (hooks.trigger("permission", "evaluate", {sessionID, agent, action, resources[], metadata, source, effect}), mutable effect, called for real from core/src/permission.ts). So in v2 a permission plugin hook does fire — but with a different contract, and v2 has no GitHub Releases and no npm latest/beta entry yet (docs: "plugin APIs may continue to change").

    Net: two possible resolutions now — (A) #47675 reviewed + shipped on 1.18.x, or (B) v2 reaches a release channel with permission.evaluate stable. Until one of those happens, plugins relying on the documented permission.ask remain inert no-ops on everything installable. Request remains the same: a maintainer pass on #47675 before the stale window closes; and if v2 is the answer, a migration note for permission.ask → permission.evaluate in the v2 release notes.

  20. neriousy commented on Oct 1, 2026

    @neriousy
    Member

    OpenCode v2 replaces this with the permission.evaluate plugin hook, added in #45003. It runs each time a permission is checked and lets a plugin change the result and its message, so you can auto-approve or deny requests from a plugin.

    This is v2 only. The 1.x permission.ask hook still doesn't fire.

    Closing as completed. If something doesn't work on v2, please open a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions