You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Restores the declared permission.ask plugin hook before OpenCode creates an interactive permission request. Static deny remains final, static allow bypasses the hook, and plugins can resolve an ask to allow or deny.
This follows #39442 but preserves Effect interruption instead of turning cancellation into a pending prompt. Ordinary plugin failures fail closed to the normal prompt, and structuredClone prevents nested plugin mutation of the retained request. The SDK hook input uses the current v2 PermissionRequest type.
How did you verify your code works?
Added tests for allow, deny, failure fallback, interruption without a pending request, nested metadata isolation, and sequential multi-plugin overrides. Ran 86 permission tests (132 assertions), plugin trigger tests, OpenCode/plugin typechecks, the plugin build, and format/diff checks.
AI code review — automated review for reference; please use your judgment.
Scope: restores the permission.ask plugin hook: plugins receive a structuredClone of the request info and may set output.status to "allow"/"deny"; anything else falls through to the interactive prompt.
Security posture is right where it matters most: plugin failure resets to "ask" (interactive prompt) rather than failing open to allow, interrupts propagate instead of being swallowed, and structuredClone(info) stops plugins from mutating shared request state. A crashing plugin degrades to the pre-hook behavior — exactly what "restore safely" should mean.
Cause.hasInterrupts re-failure distinction is a nice touch; generic catch-all handlers that also eat interrupts are a common Effect bug.
Design question worth confirming: the hook only runs when the configured rules resolved to "ask", so plugins can upgrade an ask → allow/deny but can never tighten an explicit config allow/deny. That seems like the intended layering (config is authoritative, plugins advise) — just make sure docs say plugins cannot restrict what config allows.
Test-infra nit: waitForPending timeout went 1s → 10s. Understandable with plugin loading in the loop, but blanket 10x timeouts can mask real latency regressions; consider scoping the longer timeout to the plugin-specific tests only.
The deny path rebuilds the ruleset-filtered DeniedError — consistent with native denials, so downstream consumers can't distinguish plugin denials from rule denials. Intentional? Fine either way, but worth a sentence in the PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes#7006
Type of change
What does this PR do?
Restores the declared
permission.askplugin hook before OpenCode creates an interactive permission request. Static deny remains final, static allow bypasses the hook, and plugins can resolve anasktoallowordeny.This follows #39442 but preserves Effect interruption instead of turning cancellation into a pending prompt. Ordinary plugin failures fail closed to the normal prompt, and
structuredCloneprevents nested plugin mutation of the retained request. The SDK hook input uses the current v2PermissionRequesttype.How did you verify your code works?
Added tests for allow, deny, failure fallback, interruption without a pending request, nested metadata isolation, and sequential multi-plugin overrides. Ran 86 permission tests (132 assertions), plugin trigger tests, OpenCode/plugin typechecks, the plugin build, and format/diff checks.
Screenshots / recordings
N/A — plugin API and permission-flow change only.
Checklist