Repository navigation
Description
Description
Bug Description
In opencode, message.part.updated can arrive after message.part.delta for the same
partID on the /event SSE stream. Subscribers that use message.part.updated to register
a part before processing its deltas will encounter a race where a delta arrives first.
Environment
- OpenCode version: tested on v1.14.48
- Affected path:
SyncEvent.run→Session.updatePart→ bus publish - Trigger: any streaming response that emits text or reasoning parts
Steps to Reproduce
Connect to the SSE event stream and filter for message.part.* events while a prompt is
being processed. Observe that message.part.delta events for a given partID can appear
before the first message.part.updated for that same partID.
Expected Behavior
For each partID, message.part.updated arrives before any message.part.delta with that
same ID:
data: {"type":"message.part.updated","properties":{"part":{"id":"prt_abc","type":"text",...}}}
data: {"type":"message.part.delta","properties":{"partID":"prt_abc","field":"text","delta":"..."}}
data: {"type":"message.part.delta","properties":{"partID":"prt_abc","field":"text","delta":"..."}}
Actual Behavior
message.part.delta can arrive before message.part.updated for the same partID:
data: {"type":"message.part.delta","properties":{"partID":"prt_abc","field":"text","delta":"..."}}
data: {"type":"message.part.updated","properties":{"part":{"id":"prt_abc","type":"text",...}}}
Root Cause
Session.updatePart and Session.updatePartDelta publish to the bus through different
mechanisms:
updatePartroutes throughSyncEvent.run, which defers the bus publish via
Database.effectas a fire-and-forgetrunPromisecall — the publish runs on a
separate fiber and may not complete beforeupdatePartreturns.updatePartDeltacallsyield* bus.publish(...)directly on the caller's fiber.
Even though processor.ts correctly calls updatePart before updatePartDelta, the delta's
publish can reach the PubSub first because it runs on the current fiber while the update is
still pending on its orphaned fiber.
The relevant code in sync/index.ts:
Database.effect(() => {
const result = convertEvent(def.type, event.data)
const publish = (data) => ProjectBus.publish(def, data, { id: event.id })
if (result instanceof Promise) void result.then(publish)
else void publish(result) // ← fire-and-forget, no caller awaits this
})Impact
Any subscriber that uses message.part.updated to learn a part's type before processing its
deltas will mishandle or drop the early deltas. In our integration, it causes:
AI_UIMessageStreamError: Received text-delta for missing text part with ID "prt_...".
Ensure a "text-start" chunk is sent before any "text-delta" chunks.
Plugins
No response
OpenCode version
No response
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Activity
github-actions commentedon Jul 11, 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.