Skip to content

chat.message plugin hook: SyncEvent.run: "sessionID" required but not found when pushing to output.parts #23440

Closed as not planned

Description

@pekg-ai

OpenCode version: 1.14.18

Plugins that push a text part to output.parts from inside a chat.message hook intermittently crash with:

error: SyncEvent.run: "sessionID" required but not found: {"part":{"type":"text","text":"..."},"time":<epoch>}
    at wl (/$bunfs/root/chunk-ng0j12f1.js:491:37371)
    at ~effect/Effect/evaluate (/$bunfs/root/chunk-3p3b82m7.js:25:4492)
    at runLoop (/$bunfs/root/chunk-3p3b82m7.js:25:2045)

Repro shape (simplified):

"chat.message": async (_input, output) => {
  const ctx = await fetch("https://example/api");  // ~1–3s network call
  output.parts.push({ type: "text", text: await ctx.text() });
}

Synchronous pushes appear fine; pushes after an await (network fetch here) race a closed session scope and SyncEvent.run fails because the emitted part has no sessionID.

Related:

Questions:

  1. Is pushing to output.parts in chat.message a supported API? (It's absent from the official plugin docs.)
  2. If yes, should OC capture sessionID at hook-dispatch and attach it to pushed parts so they survive async resumption?
  3. If no, what's the intended hook for injecting a per-turn text cue based on the user's message? (Feature: Include user message text in experimental.chat.system.transform input #17637 is the only current avenue and it lacks the user text.)

Happy to provide the full failing plugin source if useful.

Activity

  1. doobidoo commented on May 23, 2026

    @doobidoo

    We encountered exactly this. Beyond the sessionID crash: in some OpenCode builds, chat.message doesn't fire at all — the trigger("chat.message", ...) call site is missing from the session processor entirely. We loaded a plugin with a chat.message hook, the plugin loaded without errors ("loading plugin" in logs), but the hook was never called.

    Workaround: use the event hook instead. Listen for message.part.updated bus events:

    event: async ({ event }) => {
      if (event.type === "message.part.updated") {
        const text = event.properties.part.text
        // text has the full part content for TextPart/ReasoningPart
      }
    }

    Also: Bun's fetch() ignores tls: { rejectUnauthorized: false }. Use new https.Agent({ rejectUnauthorized: false }) from node:https for plugins calling HTTPS endpoints with self-signed certs.

    Related: #22831 (feature request to expose body to chat.message), #29022 (our findings)

  2. github-actions commented on Jul 23, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions