Summary
The tool.execute.after hook is declared in the Hooks interface in @opencode-ai/plugin but is never invoked anywhere in the OpenCode runtime. Any plugin implementing this hook will have its handler registered but silently ignored — the handler is never called.
Details
Hook declaration
The hook is correctly typed in packages/plugin/src/index.ts:
"tool.execute.after"?: (
input: { tool: string; sessionID: string; callID: string; args: any },
output: {
title: string
output: string
metadata: any
},
) => Promise<void>
The plugin.trigger mechanism in packages/opencode/src/plugin/index.ts is also correctly implemented — it passes output by reference, calls all registered handlers, and returns the (possibly mutated) output object:
const trigger = Effect.fn("Plugin.trigger")(function* (name, input, output) {
if (!name) return output
const s = yield* InstanceState.get(state)
for (const hook of s.hooks) {
const fn = hook[name] as any
if (!fn) continue
yield* Effect.promise(async () => fn(input, output))
}
return output
})
Missing call site
A search through the entire OpenCode server codebase found zero call sites for plugin.trigger("tool.execute.after", ...):
packages/opencode/src/session/processor.ts — handles tool-result stream events and calls completeToolCall(), but no hook trigger
packages/opencode/src/session/llm.ts — assembles AI SDK tools and handles streaming, no hook trigger
packages/opencode/src/tool/registry.ts — only triggers tool.definition
packages/opencode/src/session/session.ts — no plugin references at all
packages/opencode/src/agent/agent.ts — only triggers experimental.chat.system.transform
- All v2 session files — no hook trigger
Where the trigger should logically live
In packages/opencode/src/session/processor.ts, the tool-result event handler at around line 380 receives the completed tool output and calls completeToolCall(). This is the natural location to trigger tool.execute.after:
case "tool-result": {
// ... EventV2 dual-write ...
// MISSING: yield* plugin.trigger("tool.execute.after", { ... }, value.output)
yield* completeToolCall(value.toolCallId, value.output)
return
}
The value.output object has the shape { title, output, metadata } which matches the hook's declared output parameter exactly.
Impact
Plugins that implement tool.execute.after to intercept or modify tool output (e.g., to inject context, compress output, or add telemetry) have no effect. The hook infrastructure is in place but the wire-up is missing.
Proposed Fix
In packages/opencode/src/session/processor.ts, add the plugin trigger before completeToolCall:
case "tool-result": {
const toolCall = yield* readToolCall(value.toolCallId)
// ... existing EventV2 dual-write ...
// Trigger the after hook so plugins can observe/modify tool output
const hookInput = {
tool: value.toolName,
sessionID: ctx.sessionID,
callID: value.toolCallId,
args: toolCall?.part.state.status === "running" ? toolCall.part.state.input : {},
}
const hookOutput = yield* plugin.trigger("tool.execute.after", hookInput, {
title: value.output.title,
output: value.output.output,
metadata: value.output.metadata,
})
yield* completeToolCall(value.toolCallId, { ...value.output, ...hookOutput })
return
}
Similarly, tool.execute.before (also declared but never triggered) should be wired up at the tool-call event site.
Also affected
tool.execute.before — declared in Hooks but also never triggered in the runtime.
Environment
- OpenCode version: 1.14.39
- Found via: source audit of
packages/opencode/src/ on commit history at time of investigation
Summary
The
tool.execute.afterhook is declared in theHooksinterface in@opencode-ai/pluginbut is never invoked anywhere in the OpenCode runtime. Any plugin implementing this hook will have its handler registered but silently ignored — the handler is never called.Details
Hook declaration
The hook is correctly typed in
packages/plugin/src/index.ts:The
plugin.triggermechanism inpackages/opencode/src/plugin/index.tsis also correctly implemented — it passesoutputby reference, calls all registered handlers, and returns the (possibly mutated)outputobject:Missing call site
A search through the entire OpenCode server codebase found zero call sites for
plugin.trigger("tool.execute.after", ...):packages/opencode/src/session/processor.ts— handlestool-resultstream events and callscompleteToolCall(), but no hook triggerpackages/opencode/src/session/llm.ts— assembles AI SDK tools and handles streaming, no hook triggerpackages/opencode/src/tool/registry.ts— only triggerstool.definitionpackages/opencode/src/session/session.ts— no plugin references at allpackages/opencode/src/agent/agent.ts— only triggersexperimental.chat.system.transformWhere the trigger should logically live
In
packages/opencode/src/session/processor.ts, thetool-resultevent handler at around line 380 receives the completed tool output and callscompleteToolCall(). This is the natural location to triggertool.execute.after:The
value.outputobject has the shape{ title, output, metadata }which matches the hook's declaredoutputparameter exactly.Impact
Plugins that implement
tool.execute.afterto intercept or modify tool output (e.g., to inject context, compress output, or add telemetry) have no effect. The hook infrastructure is in place but the wire-up is missing.Proposed Fix
In
packages/opencode/src/session/processor.ts, add the plugin trigger beforecompleteToolCall:Similarly,
tool.execute.before(also declared but never triggered) should be wired up at thetool-callevent site.Also affected
tool.execute.before— declared inHooksbut also never triggered in the runtime.Environment
packages/opencode/src/on commit history at time of investigation