Skip to content

plugin: permission.ask is typed and documented but never triggered #46809

Closed as duplicate

Description

@lgarceau768

Description

permission.ask is in the plugin Hooks type with output: { status: "ask" | "deny" | "allow" }, and it is listed in the bundled plugin docs under "Hook surface (mutate output in place; return void)", between shell.env and the experimental.* hooks. It is never fired.

Three checks on 1.18.26, behavioural first:

1. A probe handler is never called. A plugin whose only job is to append to a file on every invocation logged nothing, while the run printed:

! permission requested: bash (git commit -m probe); auto-rejecting

2. output.status = "allow" changes nothing. Same plugin, same run: the permission request is still created and published, and still rejected.

3. No call site in the binary. Fifteen hooks appear as trigger("<name>"):

7 trigger("tool.execute.before"     1 trigger("tool.definition"
7 trigger("tool.execute.after"      1 trigger("command.execute.before"
4 trigger("shell.env"               1 trigger("chat.params"
2 trigger("file.open"               1 trigger("chat.message"
2 trigger("experimental.chat.*"     1 trigger("chat.headers"   ... etc

permission.ask has none. Of its string occurrences, 21 are the permission.asked event and exactly one is the bare hook name — in the docs list above.

Why it matters

It is the only typed allow/deny in the plugin surface, so a plugin that wants to gate one specific tool call currently has no channel at all. Throwing from tool.execute.before is not a refusal: handlers run through Effect.promise with no try/catch, so it kills the tool call as a defect and the model gets no reason it can act on.

Steps to reproduce

  1. Config: { "permission": { "bash": { "git commit*": "ask" } } }
  2. Plugin in the config dir:
    export const Probe = async () => ({
      "permission.ask": async (input, output) => {
        appendFileSync("/tmp/probe.log", JSON.stringify(input) + "\n")
        output.status = "allow"
      },
    })
  3. opencode run "run exactly this one command: git commit -m probe" in a repo with a staged file
  4. The permission is requested and rejected; /tmp/probe.log is never created.

One detail if you do wire it up

The Permission payload's patterns carries the command, not the config pattern that matched — opencode run prints it as permission requested: bash (git commit -m '...'). So a handler cannot distinguish a permission that its own plugin's config patterns caused from one the user configured for their own reasons, which matters for any plugin that wants to answer only its own.

Either outcome is fine

Wiring it up, or dropping it from the type and the docs list, both work from a plugin author's side. Right now it reads as available and silently is not.

Plugins

A private first-party plugin of my own.

OpenCode version

1.18.26 (@opencode-ai/plugin 1.18.23)

Activity

  1. github-actions commented on Sep 2, 2026

    @github-actions
    Contributor

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

  2. lgarceau768 commented on Sep 2, 2026

    @lgarceau768
    Author

    Duplicate of #7006 — closing this in favour of that one, which is eight months older and much further along. My apologies for the noise; I searched "prefill" and "permission" issue titles before filing and did not turn it up.

    For anyone who lands here first, #7006 has everything this issue was missing:

    • Root cause with a commit. The Plugin.trigger("permission.ask", ...) call site was dropped in 38e0dc9; f015154 still had it. There is also evidence in that thread that published stable builds may never have had it.
    • Five candidate PRs, of which feat(permission): restore permission ask hook safely #42633 is reported verified working on a compiled build.
    • A working workaround today — listen for the permission.asked event in the plugin's event handler rather than registering permission.ask, then answer through the reply endpoint.

    That last point is the one I would have wanted when I filed this: the hook is dead, but a plugin is not actually without a channel.

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