Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Hi — I just watched the Fireship video on OpenAPPA ("Did a 50 year old military secret just solve agent prompt injection?") and immediately thought: this would be a great thing to be able to use with OpenCode. The blocker is one missing primitive on the plugin side, which is what this issue is about. APPA just happens to be the first concrete use case I have — the change is generally useful for any plugin that wants to deterministically deny a tool call before it runs.
Related history
This has been requested before, all closed by automation rather than on the merits:
- #20013 — same
argsbeing discarded at call sites; closed by 60-day-inactivity bot. - #32565 — same primitive (
skip/resultontool.execute.before); auto-closed by the contributing-guidelines bot. - #26521 —
cancel: stringvariant of the same ask; auto-closed by the contributing-guidelines bot. - #15933 — adjacent feature request (
messageIDin hook payloads); closed by 60-day-inactivity bot.
I'm refiling because the underlying primitive is still missing, and the use case (deterministic pre-execution gating) is what every policy/safety plugin needs. Happy to close this as a duplicate of one of the above if a maintainer points at a preferred canonical issue.
What's missing
A plugin can hook tool.execute.before today (packages/plugin/src/index.ts:266) and mutate output.args, but the mutated args are discarded at every call site I checked — packages/opencode/src/session/tools.ts:102-132, :175-219, :255-303, :325-385, :398-490, and packages/opencode/src/session/prompt.ts:301-380 all pass the closure-captured original args to item.execute(args, ctx) and ignore the trigger's output. So a plugin cannot currently block a tool call before it executes.
What I'd like
Add a new field to the trigger output, say skip: boolean, and honor it at each call site. Something like:
// packages/plugin/src/index.ts
"tool.execute.before"?: (
input: { tool: string; sessionID: string; callID: string },
output: { args: any; skip?: boolean },
) => Promise<void>// packages/opencode/src/session/tools.ts (illustrative)
const hookOut = { args }
yield* plugin.trigger("tool.execute.before", { tool, sessionID, callID }, hookOut)
if (hookOut.skip) {
// Synthesize a tool error the model can see.
return { title: "blocked", output: "blocked by plugin", metadata: { error: "plugin-blocked" } }
}
yield* item.execute(hookOut.args, ctx)That's it. ~30 lines across the call sites, plus a one-line doc update.
Why not permission.ask?
I considered that, but it's interactive — the user gets a prompt and the block is not deterministic. APPA's contract is "this value cannot flow to this sink, full stop" — no prompt, no override. So I can't use it without weakening the guarantee.
Why not throw?
tool.execute.before exceptions are caught and ignored by the plugin trigger (packages/opencode/src/plugin/index.ts:285-298), so throwing is silently swallowed. Not viable.
Backwards compatibility
The new skip field is opt-in (defaults to false). No existing plugin breaks.
What I'd do
If this lands, I'll maintain an OpenCode plugin that wraps OpenAPPA in the opencode-ai org. The plugin is straightforward and follows exactly the shape Amp's appa plugin uses today — OpenAPPA already serves Claude Code, Amp, and Kagent the same way.
I'm happy to:
- Open a draft PR with the change.
- Write a small unit test that asserts
skipshort-circuits the call. - Add the field to the plugin docs in
packages/docs/.
Before I do that, two questions:
- Naming.
skip?denied?output.skipis my preference because it parallelsoutput.argsand reads naturally ("the trigger skipped the call"). Open to alternatives. - Where the skip surfaces to the model. Two options: (a) silent — the tool returns a synthetic error block the model sees as
[blocked by plugin] <reason>, no TUI event; or (b) emit apermission.asked-style event so the TUI shows the block reason. Option (a) is what Amp plugins do ({ action: 'reject-and-continue' }); option (b) is more visible. Lean toward (a) for the first cut, but happy to do (b).
If this isn't a direction the maintainers want, I'd appreciate a pointer to a better extension point. The integration degrades to a post-hoc-only design without it (block-after-run), which is weaker but still shippable — so I'm not blocked on this landing, just constrained.
Thanks!
Activity
github-actions commentedon Oct 2, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
[FEATURE]:.Please edit this issue to address the above within 72 hours, or it will be automatically closed.
This issue might be a duplicate of existing issues. Please check:
tool.execute.beforeshort-circuit tool execution.tool.execute.before.If you believe this was flagged incorrectly, please let a maintainer know.
joeskeen commentedon Oct 2, 2026
FYI for anyone finding this issue first: the APPA-side companion issue is archestra-ai/OpenAPPA#590. This issue is the only upstream dependency for that integration — once
tool.execute.before(or equivalent) supports pre-execution gating, the rest of the integration is adapter + plugin work that lives entirely in the OpenAPPA repo.[-]Add a skip field to ool.execute.before (for deterministic pre-execution gating)[/-][+][FEATURE]: Add a skip field to tool.execute.before (for deterministic pre-execution gating)[/+]github-actions commentedon Oct 2, 2026
Thanks for updating the issue. It now includes the required feature-request checkbox and title format.
Konstantinov-Innokentii commentedon Oct 5, 2026
Hi! OpenAppa maintainer here. Since OpenCode has no auto mode yet, this would be a valuable addition!
In our benchmarks OpenAppa let legitimate tasks run about as well as Claude Code’s auto mode, while blocking more attacks.
We will be happy to help!
rekram1-node commentedon Oct 6, 2026
have you looked at the v2 branch? where opencode v2 publishes?
https://opencode.ai/v2/docs
I think it may have addressed all or most of your issues?
Mutating args in the hook definitely changes the execution
Would say that v2 doesnt currently expose sufficient permission hooks (landing soon).
opencode-agent commentedon Oct 6, 2026
You can do this today without a new field. Throwing from the
execute.beforetool hook rejects the call before the tool runs. Your error message goes back to the model as the tool result, so it can adjust and keep going. There's no user prompt, so the block is deterministic.Here's a small plugin that tracks what a session has read and blocks network fetches once it has read anything under
secrets/:For an OpenAPPA integration, put your engine in place of the two checks: label in
execute.after, decide inexecute.before. Some more you can do from the same hooks:event.input = { ...sanitized }inexecute.before, and the tool runs with the new input.ctx.permission.hook("evaluate", ...)can setevent.effect = "deny"withevent.messageas the reason, without prompting the user.See the plugin docs for the full hook reference.
Konstantinov-Innokentii commentedon Oct 6, 2026
@rekram1-node thanks!
I didn't get this part though. V2 exposes more hooks and landing soon, but still not allow mutating tool call args?
cc @arsenyinfo
rekram1-node commentedon Oct 6, 2026
V2 doesn't expose the permission ask hooks rn, so u cant write custom things that interact w/ the permissions api directly (yet)
But v2 does allow u to mutate args, intercept, change, throw, etc on tool calls.
I think largely v2 is much more plugin friendly but there are some services we havent directly added to plugin surface yet like permissions but it is trivial to do so.
Konstantinov-Innokentii commentedon Oct 6, 2026
@rekram1-node when you say permissions api, you mean some opencode api which allows to ask user interactively if they are fine with doing some action?
I think for OpenAppa <-> Claude Code integration we had to hack around with MCP elicitation to show some approval forms, but I'm pretty sure it will not be a huge bump on the road.
opencode-agent commentedon Oct 17, 2026
Closing this out: everything requested here is now available in OpenCode v2 (
dev, published since v2.0.29).Deterministic pre-execution blocking. Throwing from the
execute.beforetool hook rejects the call before the tool runs. The error message is returned to the model as the tool result, and the user is never prompted. In v2, hook errors are no longer swallowed the way the v1 trigger swallowed them.Mutating args. Assign
event.inputinexecute.before, and the tool runs with the new input. This covers sanitizing instead of denying.Permissions. This was the remaining gap mentioned earlier in the thread. It has now landed:
ctx.permission.hook("evaluate", ...): inspect a permission evaluation (action, resources, metadata) and setevent.effect = "deny"withevent.messageas the reason, with no user prompt. You can also set"allow"or"ask".ctx.permission.assert(...)(feat(plugin): let plugins assert permissions #54930): evaluates the session's rules and, when approval is required, prompts the user and waits for the reply. @Konstantinov-Innokentii, this answers your question: yes, this is the interactive "ask the user" API, so there's no need for an elicitation workaround like the one in Claude Code. A deny or a rejection with feedback rejects with aTool.Errorcarrying the reason.ctx.permission.list/get/replyfor working with pending requests.For an OpenAPPA integration, label in
execute.after, decide inexecute.before(throw to deny, rewriteinputto sanitize), and usepermission.assertwhen a human should confirm instead of a hard block.See the plugin docs at https://opencode.ai/v2/docs for the full hook reference. If something is still missing once you build against v2, please open a new issue with the specific gap.