anomalyco / anomalyco/opencode
web: sustained ~50% CPU and maxed GPU while live-rendering a streaming session
@Brendonovich is already working on this.
Since Sep 17, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
The v2 web client sustains a large fraction of a CPU core and keeps the GPU at its maximum clock for the entire duration of a streaming session, then drops to ~0 when the session is idle. The load is disproportionate for streaming text and scales with session content size; it appears to come from per-event timeline re-rendering without batching or throttling.
Environment
- opencode version: 2.0.5 (channel: latest; 2.0.6 does not change this code path — I audited the full v2.0.5...v2.0.6 diff before reporting)
- OS: Linux 7.2.6-arch2-1 (Arch Linux, x86_64), KDE Plasma on Wayland, hybrid laptop (Intel UHD iGPU + RTX 3050); the browser composites on the iGPU
- Web client: Firefox 156.0 (opencode web UI served locally by
opencode serve, paired viaopencode pair) - Install/channel: Arch Linux package (pacman), v2 release channel
- Active plugins: none
Reproduction
- Start the background service (
opencode service start) and open the web client in a browser using the credentials fromopencode pair. - Stream a long agent turn in the session that the web tab displays: any long response, amplified by large tool outputs (e.g. a
catof a multi-MB file). - While streaming, watch:
- the Firefox content process CPU (
ps -eo pid,pcpu,args | grep 'firefox -contentproc'), and - the iGPU: KDE GPU-load widget, or
cat /sys/class/drm/card1/gt_cur_freq_mhz(compare againstgt_max_freq_mhz).
- the Firefox content process CPU (
- Let the session go idle; observe the load fall back to baseline.
Consistently reproducible: the load tracks streaming activity 1:1 (high while any event for the displayed session is being rendered, ~0 when idle).
Expected Behavior
Rendering the live timeline while a session streams should cost on the order of a terminal streaming text: brief, batched work per chunk, with the GPU and CPU near idle between bursts.
Actual Behavior
While the displayed session streams (measured on 2.0.5, repeated runs):
| State | Firefox content CPU | iGPU (Alder Lake UHD, max 1300 MHz) |
|---|---|---|
| Web tab closed | ~0 | 300–600 MHz (idle) |
| Web tab open, session idle | ~0 | near-0 utilization |
| Web tab open, session streaming | ~35–50% of one core, nearly constant | pinned at 1300 MHz (max) |
The KDE GPU widget correspondingly shows the iGPU near 100% for the whole duration of the stream, even though no interaction with the page is happening.
Additional Context
Related issues:
- #48434 — web, closest mechanism: timeline row reconciliation (Effect deep equality) on every stream delta; fix PR #48435. This report additionally covers the observer→rAF re-measure and scroll-reconcile chains, the resulting GPU compositing load, and the idle baseline.
- #31664 — v1 Desktop app (Electron, Windows): GPU load from continuous CSS animations while "working" — different client and root cause.
- #43355 — Desktop renderer frozen by an escalating ResizeObserver loop — same fragile path; here it is sustained heavy load, no freeze.
- #46341 — server-side sustained CPU/RSS and SQLite event-table bloat — different layer than client rendering.
- #14114 — v1-era web UI SSE streaming bandwidth/CPU — closed 2026-07-04, not planned, unresolved.
- #31548 — v1-era "Web UI: high CPU during streaming, layout thrashing" (per-delta Shiki WASM on main thread, dual store mutations, scroll-anchor rAF thrashing, non-virtualized diffs) — auto-closed 2026-08-09, not planned, unresolved.
Static analysis of the 2.0.5 web bundle (served assets) shows the mechanism:
- The web client subscribes to the server's global event stream (with stall-detection abort + reconnect/backoff).
- The session route renders the timeline as a virtualized list. Each streamed event mutates the timeline DOM, which re-triggers a chain of observers that schedule
requestAnimationFramework:ResizeObserver→ rAF row re-measure (resizeItem/ height recalculation) on the virtualized list,MutationObserveron the timeline container (childList/subtree/characterData) → rAF scroll/reveal reconciliation,- message-count change → rAF scroll-position restore,
- a history-preload rAF chain that keeps firing while the viewport is not full.
- I found no batching, coalescing, or idle-throttling of these re-render paths in the 2.0.5 bundle (all
setIntervalsites are legitimate: a 1s shell-output poller while a shell tool runs, a retry countdown; no unconditional idle loop exists — the load strictly correlates with incoming events).
Notes:
- The cost scales with content size: the measurements above were taken on a session containing several multi-MB tool outputs; even so, ~50% of a core + pinned-max GPU for a text stream looks well above the expected cost.
- 2.0.6 audit: the only session-timeline changes in v2.0.5...v2.0.6 are attachment-card display (
SessionUserAttachmentReference); no render-pipeline changes. - Workaround: closing the web tab (or navigating away from the active session) stops the load immediately.
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.