[Bug]: Desktop renderer burns 50-230% CPU while agent-run indicators are lit (infinite non-composited animations)
Deployment type
self-host — Windows 11 + local PostgreSQL + local daemon, desktop app v0.4.44 (same build as the current latest release; the animation definitions referenced below were verified inside the shipped app.asar, not only on main).
Area
Desktop app
What happened?
While agent runs are in flight and the app sits on an issue page whose "agent working" indicators are lit, the desktop renderer process burns 50–230% of a CPU core continuously (measured peak 232.8%, i.e. ~2.3 cores), the GPU process adds another 25–45%, and the renderer's working set sawtooths between ~600 MB and ~1.5 GB. When the same page has no active/recent agent run associated with it, the same process settles at 0.3–4% (GPU process 0%).
On a machine that is also running an IDE, a terminal, and several agent processes, this is enough to make the whole desktop lag — the user-visible symptom is the mouse stalling for seconds and clicks not registering.
Measured over a 27-minute window (local time 19:51–20:19, 199 samples, one sample every 5 s) while 6 agent tasks ran in the workspace:
- 37% of all samples were above 0.5 core; 22% of all samples were above a full core
- busy windows: renderer avg 98%, max 232.8%, GPU process 25–45%
- idle windows (no agent activity associated with the displayed page): renderer 0.3–4%, GPU process 0%
- renderer working set: min 595 MB, max 1491 MB
Two distinct cost components are visible in the samples:
- Continuous repaint. The GPU process sits at 25–45% for the whole time the page is in its "live" state and drops to 0% when it is not. That is consistent with the always-on animations listed below (rotating conic-gradient border, text shimmer, box-shadow pulse) — none of them can be composited, so each one repaints every frame.
- Sustained JS that is not message-driven. Example sample: at 19:59:56 (raw row below) the daemon appended 131 bytes to its log in that 5 s window — i.e. no agent output — yet the renderer was still at 92.5% while the GPU process was only at 4.1%. So this part is not "one message in, one render out"; something keeps the JS main thread busy for as long as a live indicator is lit.
Steps to reproduce
- Sign in to a workspace that has agents/squads executing tasks (one run in
running, or one that just finished, is enough). - Open an issue detail page whose issue — or one of its sub-issues — has that live run, so the "working/queued" indicators, the header-chip beam and/or the chat FAB pulse are lit.
- Watch the
Multica.exe --type=rendererprocess (Task Manager, orGet-Process -Name Multica): CPU climbs to ~1–2.3 cores and stays there. - Let all runs finish (indicators go dark) but keep the same page open: CPU falls to <5% and the GPU process to 0%.
Notes on the instance used here: the page is an issue with ~160 comments and a handful of sub-issues; the workspace runs a squad whose agents hand off stage after stage, so the indicators are effectively lit most of the working day. The window is 1936×1096 CSS px at device-scale-factor=2, i.e. every repaint is 4× the pixel work of a 1× 1080p window. The same shape (0.5–2.3 cores continuously, GPU 25–45%) was also recorded in a separate 1-hour run earlier the same day.
Screenshots (optional)
No screenshot. Raw 5-second sampling CSV available on request (columns: timestamp, active page title, per-process CPU% + working set, daemon log bytes appended, delta).
Additional context (optional)
# Environment
OS: Windows 11
Desktop app: 0.4.44 (same as the latest release)
Deployment: self-host (server + daemon + desktop on the same machine)
Server: 127.0.0.1:8080 ; daemon profile desktop-127.0.0.1-8080
Window: 1936x1096 CSS px, device-scale-factor=2
Page open: an issue detail page (164 comments, several sub-issues)
# Sampling method: PowerShell loop, one sample / 5 s.
# renderer = the Multica process with the largest working set (PID 30964 here)
# GPU = the Multica gpu-process (PID 37972)
# activity = bytes appended to ~/.multica/profiles/desktop-127.0.0.1-8080/daemon.log per sample
# Agent task timeline inside the same window (from daemon.log)
19:50:22 task picked agent=squad lead agent parent issue
19:52:54 task picked agent=squad lead agent parent issue
19:55:00 task picked agent=requirements agent a new sub-issue
20:05:16 task picked agent=squad lead agent parent issue
20:06:00 task picked agent=dev agent another new sub-issue
20:12:35 task picked agent=squad lead agent parent issue
# 6 tasks finished in the window; durations 1m15s, 1m36s, 1m19s, 2m37s, 6m39s, 10m29s
# Raw samples (renderer % / GPU process % / daemon bytes per 5 s)
19:51:29 210.6% gpu 35.9% +1919 B
19:55:23 232.8% gpu 33.4% +9990 B <-- measured peak (~2.3 cores)
19:59:56 92.5% gpu 4.1% +131 B <-- no message traffic, still ~1 core
20:04:08 0.6% gpu 0.0% +1878 B <-- indicators dark, same page open
20:12:30 0.6% gpu 0.0% +2518 B
20:12:47 128.4% gpu 0.0% +54132 B <-- 17 s later, a run starts streamingWhere the CPU goes — code pointers (primary suspects)
All three "alive" indicators are infinite, non-compositable CSS animations. Individually they look cheap, but they are never idle while agents work, and each frame forces style + paint work on the main thread (plus raster/composite on the GPU):
packages/views/issues/components/issue-agent-header-chip.tsx:192—anyRunning && "border-beam bg-brand/5"..border-beam:before(packages/ui/styles/base.css, ~line 195) animates a registered custom property (@property --border-beam-angle) that drives aconic-gradientcombined withmask-composite: exclude—animation: border-beam-rotate 3.2s linear infinite. Animating a custom property used inside a gradient cannot be composited, so the element is repainted every frame.packages/views/issues/components/issue-agent-activity-indicator.tsx:135andpackages/views/issues/components/sub-issues-agent-working-chip.tsx:64—animate-chat-text-shimmer(base.css, ~line 163) — a moving background revealed throughbackground-clip: text,animation: chat-text-shimmer 2.5s linear infinite→ repaint every frame. Note this indicator subscribes to the workspace-wideagentTaskSnapshotOptionsquery (narrowed per row by PR #5338), so it is lit by any running task in the workspace.packages/views/chat/components/chat-fab.tsx:73—isRunning && "animate-chat-impulse"(base.css, ~line 132) — animatesbox-shadow+colorinfinitely → repaint every frame.
All three animation definitions, including the infinite timing functions, exist in the shipped 0.4.44 app.asar.
What has not been verified: an actual JS profile of the renderer. The split between (a) the animation repaint and (b) the sustained JS in item 2 above is inferred from the GPU-vs-CPU delta plus the code pointers, not from a flame graph. If a renderer stack sample while CPU is high would help — the artifact requested in #3345 / #3699 — I can capture one (Windows) during a busy window and attach it.
Related: #3345 and #3699 were both closed without a diagnosis; the maintainer replies there asked for exactly this kind of data (which screen was open, whether tasks were running, a renderer process sample). PR #5338 (MUL-4474, slice 1) already de-amplified the per-row activity indicator on the re-render dimension; the continuous animation dimension described here is still present in 0.4.44.
Source: multica-ai/multica