Repository navigation
Description
Problem
Plugins that subscribe to tool.execute.before receive output.args and can mutate it, but the tool still executes with the original args from the closure — not the mutated ones.
// tools.ts (before fix)
plugin.trigger("tool.execute.before", { tool: item.id, ... }, { args }) // ← inline object
item.execute(args, ctx) // ← still uses the original args, not the mutated onesThe trigger creates a fresh { args } object each time, so when the plugin mutates output.args.command, it mutates that throwaway object — the original args variable in the closure is never updated.
Root Cause
Three call sites pass an inline object literal { args } to plugin.trigger(), then use the original args for execution instead of reading back from the hook output.
Fix
Capture the hook output in a variable, pass that variable to trigger(), then use hookOutput.args for execution:
--- a/packages/opencode/src/session/tools.ts
+++ b/packages/opencode/src/session/tools.ts
@@ -83,13 +83,14 @@ builtin tools
+ const hookOutput = { args }
const ctx = context(args, options)
yield* plugin.trigger(
"tool.execute.before",
{ tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID },
- { args },
+ hookOutput,
)
- const result = yield* item.execute(args, ctx)
+ const result = yield* item.execute(hookOutput.args, ctx)
@@ -124,15 +125,16 @@ MCP tools
+ const hookOutput = { args }
const ctx = context(args, opts)
yield* plugin.trigger(
"tool.execute.before",
{ tool: key, sessionID: ctx.sessionID, callID: opts.toolCallId },
- { args },
+ hookOutput,
)
- return yield* Effect.promise(() => execute(args, opts))
+ return yield* Effect.promise(() => execute(hookOutput.args, opts))--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -288,10 +288,11 @@ TaskTool
+ const taskHookOutput = { args: taskArgs }
yield* plugin.trigger(
"tool.execute.before",
{ tool: TaskTool.id, sessionID, callID: part.id },
- { args: taskArgs },
+ taskHookOutput,
)
- .execute(taskArgs, {
+ .execute(taskHookOutput.args, {Affected Files
| File | Call site |
|---|---|
packages/opencode/src/session/tools.ts |
Builtin tools (~L86) |
packages/opencode/src/session/tools.ts |
MCP tools (~L128) |
packages/opencode/src/session/prompt.ts |
TaskTool (~L291) |
Plugins
No response
OpenCode version
1.17.1
Steps to reproduce
Reproduction
Use a plugin that rewrites bash tool commands via tool.execute.before:
export const DemoPlugin: Plugin = () => ({
"tool.execute.before": async (input, output) => {
if (input.tool === "bash") {
output.args.command = "echo 'intercepted'"
}
},
})Expected: command is rewritten to echo 'intercepted'
Actual: original command runs unchanged
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Activity
github-actions commentedon Jun 10, 2026
This issue might be a duplicate of existing issues. Please check:
output.argsmutations intool.execute.beforenot propagated to tool executiontool.execute.beforecannot replace missing tool call argumentsPlease review these issues and consider closing this one if it is already covered.
fix(guild): register capitalized subagent_type alias for all builtin …
github-actions commentedon Aug 10, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.