Skip to content

permission.ask plugin hook is never triggered #47674

Closed

Description

@Atmostone

packages/plugin/src/index.ts:261 declares a permission.ask hook:

"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>

Nothing ever calls it. trigger( appears in six places across the repo —
shell.env (x2), experimental.chat.system.transform,
experimental.chat.messages.transform (x2) and tool.definition — and
permission.ask is not among them.

So a plugin that registers the hook is silently ignored: no warning, no error,
the handler simply never runs.

Reproduce

Plugin:

export default async () => ({
  "permission.ask": async (input, output) => {
    console.log("permission.ask fired", input.permission)
    output.status = "deny"
  },
})

Point opencode.json at it and ask the agent to run any shell command. The
command runs, nothing is logged, and the hook never fires.

Why it matters

There is no other interception point for permission decisions. Denying has to
be done by throwing from tool.execute.before, allowing has to be done by
auto-answering the permission.asked event after the prompt has already
appeared, and permission requests that are not tool calls (external_directory,
sandbox escalation) never reach a plugin at all.

Happy to send a PR — I have one working.

Activity

  1. github-actions commented on Sep 6, 2026

    @github-actions
    Contributor

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

    Please review these before proceeding, and consider adding your findings or a +1 to one of the existing issues instead.

  2. rekram1-node commented on Oct 14, 2026

    @rekram1-node
    Collaborator

    In OpenCode V2 the replacement is ctx.permission.hook("evaluate"), which runs for every permission check (tool calls and external_directory) before any prompt is shown:

    export default {
      id: "my-policy",
      async setup(ctx) {
        await ctx.permission.hook("evaluate", async (event) => {
          // event: { sessionID, agent, action, resources, metadata, source, effect, message }
          if (event.action === "shell" && event.resources.some((r) => r.startsWith("rm "))) {
            event.effect = "deny" // or "allow" to skip the prompt, or "ask" to force one
            event.message = "Blocked by policy"
          }
        })
      },
    }
    

    Setting allow skips the prompt, deny blocks the action with your message, and ask forces a prompt. An explicit deny in config is final and doesn't reach the hook. Docs: Plugins → Permission hooks and Migrate plugins from V1.

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