anomalyco / anomalyco/opencode
[BUG]: message.updated.1 re-serializes summary.diffs on every update, making event writes quadratic in diff size
@jlongster is already working on this.
Since Aug 15, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
message.updated.1 carries a complete message snapshot, and for user messages that snapshot includes info.summary.diffs — the full per-file diff list with patch text. Because a snapshot is written on every update to the message, the bytes written for one message grow as O(updates x final diff size).
This is a distinct contributor from the accumulated-streaming-text growth already tracked in #33356 and #41175. Those describe assistant messages growing as streamed text accumulates. This is a derived field on user messages, recomputed and reattached wholesale on each pass, and it produces the largest individual events on the box.
Measured on a 51.3 GB opencode.db (~10-14 concurrent processes, one shared file):
| table | rows | data |
|---|---|---|
event |
3,103,138 | 43.96 GB |
part |
1,251,684 | 3.0 GB |
message |
309,718 | 1.7 GB |
Average payload by event type, sampled over the newest 50,000 rows:
| type | n | avg bytes | max bytes |
|---|---|---|---|
message.updated.1 |
17,249 | 40,634 | 9,662,239 |
message.part.updated.1 |
28,043 | 1,323 | 724,969 |
session.updated.1 |
4,674 | 698 | 4,776 |
Composition of the largest observed event (13.8 MB):
sessionID: 32 bytes
info: 13,790,816 bytes
id, sessionID, role, time, agent, model ~180 bytes total
summary: 13,790,560 bytes
diffs: 13,790,549 bytes <- 99.996% of the event
summary.diffs was 389 entries of {file, patch, additions, deletions, status}, individual patches up to 1.1 MB.
The quadratic part. Grouping message.updated.1 by info.id:
| message | update events | total bytes written | peak snapshot |
|---|---|---|---|
msg_f7ba67… |
236 | 1,953 MB | 9 MB |
msg_ff59bd… |
156 | 1,842 MB | 13 MB |
msg_f6a79f… |
222 | 1,687 MB | 8 MB |
One message whose final state is 9 MB wrote roughly 2 GB of event rows. Worst single session: 65,437 events / 17.33 GB against 401 MB of durable state — 41x amplification, and 39.4% of all event bytes from 0.5% of event rows.
Why it becomes a hard failure rather than only disk use. A multi-megabyte row write holds the WAL write lock long enough to push concurrent processes past busy_timeout (5000 ms, set in packages/core/src/database/database.ts). An independent report on the same box captured a turn dying with Failed to execute statement after a 7.1 MB provider response had streamed successfully — tokens spent, persistence lost — inside a 900 ms window where three other processes logged database is locked.
Source
packages/opencode/src/session/summary.ts assigns the recomputed array back onto the message summary and updates the message:
target.info.summary = { ...target.info.summary, diffs: msgDiffs }
yield* sessions.updateMessage(target.info)
updateMessage publishes MessageUpdated with the whole info (packages/opencode/src/session/session.ts), so every diff recomputation re-serializes every patch.
Suggested fix
Diffs are derived state — recomputable from snapshots via snapshot.diffFull. They do not need to travel through the event bus. Storing them in a side table keyed by message id, keeping counts (additions/deletions/files) on the summary, and publishing a thin { sessionID, messageID } notification that clients use to fetch on demand, takes the average message.updated.1 payload from ~41 KB to ~1-2 KB without changing what any consumer can render.
Note for anyone attempting this: four client sites read summary.diffs off the delivered message object rather than refetching (packages/app/src/pages/session/timeline/rows.ts, packages/app/src/pages/session.tsx, packages/session-ui/src/components/session-turn.tsx, packages/session-ui/src/components/message-nav.tsx). Removing the payload without a client fetch path blanks all four, and it fails silently — the timeline gates on diffs.length > 0, so the row simply stops appearing.
OpenCode version
1.18.15
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.