Repository navigation
Description
Problem
Plugins can inspect a tool call in tool.execute.before, but they cannot pause that call and request OpenCode’s native once / always / reject permission UI. They can only silently allow or throw a hard denial.
That blocks dynamic safety policies where risk is known only after plugin-side analysis. For example, a plugin can run git diff --diff-filter=D HEAD...<merge-ref> before git merge <merge-ref> and determine that the merge deletes 65 files. The desired behavior is to show a native approval prompt containing that computed reason, then continue the original tool call only when approved.
Proposed API
Expose an async permission-request capability to tool.execute.before, either as input.ask() or through an equivalent plugin context API.
"tool.execute.before": async (input, output) => {
if (input.tool !== "bash") return
const command = output.args.command
const deleted = await countDeletedFiles(command)
if (deleted <= 50) return
await input.ask({
permission: "bash",
patterns: [command],
reason: `This merge would delete ${deleted} files. Review before continuing.`,
})
}The request should use the existing native permission flow:
once: continue this tool callalways: persist the normal session-scoped approval for the supplied patternreject: return a permission-denied tool result and do not execute
Acceptance criteria
- A plugin can request approval from
tool.execute.beforeafter inspecting validated tool args. - The request uses the existing TUI/Web permission UI rather than a plugin-specific dialog.
- Approval resumes the original pending tool call without asking the model to issue it again.
- Rejection prevents execution and gives the model a human-readable reason.
- Explicit config
denyrules remain terminal and cannot be bypassed by plugins. - Plugin request failures fail closed to the normal permission prompt, not silent allow.
- Tests cover once, always, reject, explicit-deny precedence, and plugin failure.
Related work and distinction
permission.askplugin hook is defined but not triggered #7006 / fix(opencode): add permission.ask plugin hook back #19453: wirepermission.askso plugins can intercept existing staticaskdecisions. This proposal needs the inverse direction: a plugin creates a new approval request after dynamic analysis.- [FEATURE]: Wire the permission.ask plugin hook #22311: requested the same
ask()capability fromtool.execute.before; it was auto-closed for inactivity. This is a refreshed v2/dev request with concrete dynamic-deletion semantics and acceptance criteria. - [FEATURE]: Dynamic permission scoping via plugin state (permission.ask + structured DeniedError reason) #22558 / fix(opencode): wire permission.ask plugin hook with reason support #22619: dynamic permission scoping and the Permission↔Plugin dependency issue. This proposal should avoid making Permission depend directly on Plugin at module initialization. A request function can be supplied from the tool execution / prompt layer instead.
Why this is general-purpose
Beyond deletion thresholds, this enables policy plugins for schema migration review, release/cutover gates, staged write access, and external governance systems. The plugin decides when a tool call needs human review; OpenCode remains the authority that renders and records the approval.
Activity
TTTPOB commentedon Jul 27, 2026
I think this issue covers the core missing capability. I would like to add a related runtime-sandbox use case that may affect where the API should live.
Runtime use case
A security plugin such as opencode-landstrip can sandbox each Bash tool call with Landlock, seccomp, Seatbelt, or AppContainer.
Some permissions cannot be determined in tool.execute.before. The sandbox only discovers the actual resource after the process starts, for example when seccomp intercepts:
openat("/home/user/.ssh/config")
connect("127.0.0.1:5432")
The sandbox broker must hold that operation, request approval through OpenCode, and then resume or deny the operation.
Today, opencode-landstrip handles this through a TUI-specific dialog. That works in the official TUI, but SDK clients such as OpenChamber cannot render the prompt without Landstrip-specific integration.
Current API status
OpenCode already contains most of the required pieces:
Plugin-defined tools receive an awaitable context.ask().
The internal v2 permission service provides assert(), which evaluates policy and waits for a reply when the result is ask.
The v2 SDK exposes session.permission.create().
Standard permission requests already have events, pending-request listing, and reply APIs.
However:
tool.execute.before does not receive the same permission capability as a plugin-defined tool.
A server plugin cannot access an awaitable permission requester outside its own custom tool body.
session.permission.create() returns { id, effect } immediately. A plugin must implement its own event subscription, deferred map, cancellation, reconnect handling, and cleanup to wait for the reply.
The injected PluginInput.client still uses the legacy SDK client, while the v2 client is a separate export.
related: landstrip/landstrip#52
I might add more thoughts here.
jarkkojs commentedon Jul 28, 2026
There is a semantic difference with the permissions:
It is also good to keep in mind that:
Finally, consider this:
Sandbox permissions need to be separate too because agent permissions we can predict but tool commands can be any possible random executable; or even malware. Malware and other misbehaving executable are the root reason why the sandbox makes sense in the first place.
Thus, also permission queries emit from exactly two disjoint stimulis:
So I'm not sure what the correct actions are, just underlying important details and differences.
TTTPOB commentedon Jul 28, 2026
@rekram1-node, since you are assigned to this issue, I would appreciate your thoughts on whether this capability should be limited to tool.execute.before or exposed as a reusable plugin-level requester.
I think this is somewhat important since opencode lacks os-level enforcement of permission settings. Exposing this capability would leave room for sandbox and policy plugins to provide that enforcement cleanly in the future.
I may not have time (also experience) to participate deeply in the API design in the near term, but I wanted to document the runtime-sandbox requirement before the V2 plugin boundary becomes fixed. I would be happy to provide additional details about the Landstrip use case later if useful.
jarkkojs commentedon Jul 28, 2026
https://github.com/landstrip/landstrip/tree/main/packages/pi-landstrip#permission-model
This for pi-landstrip's subagents but highlights pretty well important details and differences between agent and tool command permissions.
mason: classify oc2 bash host failures
mason: reclassify every 37164 row as applicable; the capability is ou…
vedantp7600-afk commentedon Oct 12, 2026
Hey! I came across your OpenCode issue about allowing security plugins to request native approval after inspecting a tool call.
I'm building Revo Security, a runtime protection layer for AI coding agents. I'm exploring how to make risk detection and human approval work reliably.
I'd love to hear whether you've found a good approach to this problem and what you think existing solutions are missing.
rekram1-node commentedon Oct 14, 2026
As of v2.0.29, a
tool.execute.beforehook can callctx.permission.assert(#54930) and wait for the answer. Approving lets the original call run, rejecting blocks it and gives the model the reason, and configdenyrules stay final. For example:Closing this, thanks for the clear acceptance criteria!