Repository navigation
Description
Description
Plugin tools cannot publish metadata while they are running.
A plugin call to context.metadata(...) does not update the running ToolPart. The metadata only becomes visible when the tool completes and returns it as part of its final structured result.
This breaks clients that use live tool metadata. For example, a foreground task plugin publishes a child sessionId before starting the child prompt. OpenChamber cannot subscribe to that child session while the task runs, so it displays Waiting for subagent activity.... Once the task completes, the returned metadata appears and the child activity becomes available.
Related issue #12527 fixed preservation of metadata in the completed result. This report concerns live context.metadata() calls not executing.
Likely cause
The internal tool context defines metadata() as an Effect:
opencode/packages/opencode/src/tool/tool.ts
Lines 36 to 46 in 127bdb3
| export type Context<M extends Metadata = Metadata> = { | |
| sessionID: SessionID | |
| messageID: MessageID | |
| agent: string | |
| abort: AbortSignal | |
| callID?: string | |
| extra?: { [key: string]: unknown } | |
| messages: SessionV1.WithParts[] | |
| metadata(input: { title?: string; metadata?: M }): Effect.Effect<void> | |
| ask(input: Omit<PermissionV1.Request, "id" | "sessionID" | "tool">): Effect.Effect<void> | |
| } |
The public plugin context defines it as a synchronous void function:
opencode/packages/plugin/src/tool.ts
Lines 8 to 20 in 127bdb3
| * Current project directory for this session. | |
| * Prefer this over process.cwd() when resolving relative paths. | |
| */ | |
| directory: string | |
| /** | |
| * Project worktree root for this session. | |
| * Useful for generating stable relative paths (e.g. path.relative(worktree, absPath)). | |
| */ | |
| worktree: string | |
| abort: AbortSignal | |
| metadata(input: { title?: string; metadata?: { [key: string]: any } }): void | |
| ask(input: AskInput): Promise<void> | |
| } |
In fromPlugin(), OpenCode bridges the Effect-based ask() method to a Promise, but spreads the Effect-based metadata() method into the plugin context without executing it:
opencode/packages/opencode/src/tool/registry.ts
Lines 138 to 149 in 127bdb3
| execute: (args, toolCtx) => | |
| Effect.gen(function* () { | |
| // Bridge the host's Effect-based `ask` into a Promise-returning | |
| // function for the plugin to make sure context persists | |
| const bridge = yield* EffectBridge.make() | |
| const pluginCtx: PluginToolContext = { | |
| ...toolCtx, | |
| ask: (req) => bridge.promise(toolCtx.ask(req)), | |
| directory: ctx.directory, | |
| worktree: ctx.worktree, | |
| } | |
| const result = yield* Effect.promise(() => def.execute(args as any, pluginCtx)) |
const bridge = yield* EffectBridge.make()
const pluginCtx: PluginToolContext = {
...toolCtx,
ask: (req) => bridge.promise(toolCtx.ask(req)),
directory: ctx.directory,
worktree: ctx.worktree,
}At runtime, context.metadata(input) returns an Effect that the plugin API caller cannot observe or execute.
A possible bridge is:
const pluginCtx: PluginToolContext = {
...toolCtx,
ask: (req) => bridge.promise(toolCtx.ask(req)),
metadata: (input) => {
bridge.fork(toolCtx.metadata(input))
},
directory: ctx.directory,
worktree: ctx.worktree,
}Steps to reproduce
Create a plugin tool:
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Test live plugin metadata",
args: {},
async execute(_input, context) {
context.metadata({
title: "Running",
metadata: { phase: "running" },
})
await new Promise((resolve) => setTimeout(resolve, 5000))
return {
title: "Done",
metadata: { phase: "done" },
output: "done",
}
},
})- Invoke the tool.
- Inspect the ToolPart during the five-second delay.
- Observe that the title and metadata are not updated.
- Wait for completion.
- Observe that the final returned title and metadata appear.
Expected behavior
context.metadata() updates the running ToolPart immediately, matching the public plugin API contract.
Actual behavior
The running ToolPart remains pending without the supplied metadata. Only metadata returned in the completed structured result appears.
OpenCode version
1.18.3
The same unbridged implementation exists in 1.18.2 and the current dev branch.
Client
OpenChamber 1.16.2, using live state.metadata.sessionId to display foreground subagent activity.
Operating system
Linux
Terminal
N/A
Activity
github-actions commentedon Jul 20, 2026
This issue might be a duplicate of existing issues. Please check:
github-actions commentedon Sep 20, 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.