Repository navigation
Description
Description
session.hook("context") receives messages that only carry { id, role, content }. Since #61018 ("refactor(ai): remove unused metadata fields", first released in 2.2.6) Message.metadata no longer exists on those objects, so a plugin cannot attach context to a specific stored user message any more.
That is the documented v2 pattern for per-message context injection: query on the prompt hook, store the result on event.metadata, then inject it before that same message on every model step. The host still persists metadata with the stored message and ctx.session.context() still returns it, but the context hook payload no longer has it, so the injection silently no-ops.
The asymmetry is the whole bug: the field is writable on prompt, readable through session.context(), and absent on the one payload where plugins need it.
Plugins
@openviking/opencode-plugin 2026.11.10 (session-start profile + per-prompt recall injected this way). Any v2 plugin using prompt -> context metadata is affected the same way.
OpenCode version
2.2.7. First release with the change: 2.2.6.
Steps to reproduce
Minimal plugin:
export default {
id: "canary",
async setup(ctx) {
await ctx.session.hook("prompt", async (event) => {
event.metadata = { ...event.metadata, canary: { context: ["CANARY-TOKEN"] } }
})
await ctx.session.hook("context", async (event) => {
for (const message of event.messages) {
const blocks = message.metadata?.canary?.context
console.log("hook metadata?", Boolean(blocks))
if (message.role === "user" && blocks) {
message.content.unshift({ type: "text", text: blocks.join() })
}
}
})
},
async server() { return {} },
}- Load the plugin and ask the model to repeat
CANARY-TOKENif it can see it. hook metadata? falseon every step, and the model never sees the token.ctx.session.context({ sessionID })for the same session returns that message withmetadata.canaryintact, so the data exists and is reachable, just not from the hook.
Expected
Keep metadata on the messages handed to context (a read-only pass-through is enough), or expose another supported way for a plugin to address stored message metadata from the hook. Writing on prompt and reading back through a full session.context() fetch is a costly substitute, and plugging that hole is what every affected plugin now has to reimplement.
Actual
Silently dropped. No error, no warning, no deprecation notice.
Extra context
- refactor(ai): remove unused metadata fields #61018 documents this as a breaking change for plugins, but only in the PR body; the 2.2.6 and 2.2.7 release notes do not mention it, so downstream plugins had no signal to update.
- I am not asking to restore
@opencode/ai's Message.metadata; the request is about what the hook payload carries. - Local workaround used by the plugin: cache the blocks by message id in process memory, and fall back to
ctx.session.context()once per session when a message id is unknown (restart/resume/fork). It works, but the storage round trip is exactly what the metadata field used to avoid.
System
- OpenCode 2.2.7, macOS (Apple Silicon), Ghostty/iTerm2, plugin API v2 only (no v1 hooks in play).
Activity
AtaraxiaZ commentedon Nov 11, 2026
Downstream report with the consumer side and a verified local workaround: volcengine/OpenViking#7423
Two data points from that thread that may help triage:
metadatais still returned byctx.session.context({ sessionID }). Confirmed on 2.2.7: after a plugin reload (so any in-memory cache is gone), the stored messages came back withmetadata.openviking.contextintact, while thecontexthook payload for the same messages had none.If passing
metadatathrough on hook messages is unwanted, a documented alternative for "which stored message is this" would be enough; the plugin only needs the association, not the host's own bookkeeping.opencodejr commentedon Nov 11, 2026
Thanks for the detailed report and the canary plugin, it makes the gap easy to see.
You're right about the cause. #61018 (first released in 2.2.6) removed
metadatafrom@opencode/aimessages and stopped core from copying the stored message'smetadataonto the messages thecontexthook receives. That was a deliberate API change, listed as breaking for plugins in the PR. The plugin docs only describeevent.metadataon theprompthook as admission metadata and don't say it reachescontexthook messages. So I'm treating this as a request for the plugin API rather than a regression.The request: give
contexthook messages a supported, read-only way to reach the stored message's metadata, so a plugin can tie context to a specific user message without a fullctx.session.context()round trip. That could be a pass-through field or a per-message-id map on the hook event. Maintainers will consider it; I can't promise an outcome.Until then, the workaround you describe (cache by message id, and fall back to
ctx.session.context()once per session) is the supported route.Related, though not the same request:
contexthookprompthook not carrying the agent idrekram1-node commentedon Nov 12, 2026
This was a field that was meant to be purely internal... didn't think anyone depended on it, bad call on my part. Apologies