Skip to content

permission.ask plugin hook is bypassed for first-encounter commands (needsAsk=true) #19927

@YumaKakuya

Description

@YumaKakuya

Description

Summary
The permission.ask plugin hook in packages/opencode/src/permission/index.ts is currently wrapped in an if (!needsAsk) guard, which means it only fires for commands that already have an “allow” rule. For first-encounter commands where needsAsk=true, the hook is completely bypassed.

This effectively prevents plugins from intercepting or customizing the permission flow for first-encounter commands — the scenario where additional context from plugins is most valuable.

Current behavior
User runs new command → needsAsk=true → hook SKIPPED → standard dialog shown
User runs known command → needsAsk=false → hook fires → plugin can customize

Expected behavior
User runs any command → hook fires with current status ("ask" or "allow") → plugin can override → dialog shown accordingly

Why this matters for plugins
A plugin that wants to provide additional context about commands (e.g., risk assessment, documentation links, custom approval workflows) can only do so for commands the user has already approved. The first encounter — where additional context is most valuable — is unreachable.

Proposed fix
Remove the if (!needsAsk) guard so Plugin.trigger("permission.ask", ...) fires unconditionally. The hook already receives the current permissionStatus (“ask” or “allow”), so plugins can make informed decisions based on the existing status.

let permissionStatus: "ask" | "deny" | "allow" = needsAsk ? "ask" : "allow"

  • if (!needsAsk) {
    const hookResult = yield* Effect.tryPromise(() => Plugin.trigger(
    "permission.ask",
    { sessionID: request.sessionID, permission: request.permission, patterns: request.patterns, metadata: request.metadata },
    { status: permissionStatus },
    )).pipe(Effect.option)
    if (hookResult._tag === "Some") {
    permissionStatus = hookResult.value.status
    }
  • }

Backward compatibility
When no plugin is registered for permission.ask, behavior is identical to current
Existing plugins that only handle needsAsk=false cases continue to work (they receive status: "allow" as before)
The only difference is that plugins now also receive status: "ask" events they previously couldn’t see

Notes
This is a 2-line removal (the if and closing brace)
Backward compatible: no behavioral change when no plugin is registered for this hook
Minor test consideration: Permission.list() calls immediately after Permission.ask() may need to account for the hook firing asynchronously on the new path
I noticed the permission module has been undergoing some refactoring recently. Happy to adapt this proposal to align with the current direction if the approach changes.

Plugins

No response

OpenCode version

No response

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcoreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions