Claude Desktop on Linux can look healthy in logs, then freeze with placeholder skeletons or a stuck typing animation. Developer deafbeef spent two months patching a local install, captured a 1.5 TiB sparse renderer core dump (mostly reserved V8 sandbox address space, not RAM), and traced the hang to hundreds of two-megabyte shared-memory response pipes that each held the same 27-byte JSON payload: {"refresh_after_seconds":0}.
The write-up lives at blog.dfb.sh. It is not an official Anthropic post. It is one of the most detailed Chromium-in-Electron forensic stories tied to a frontier model desktop app in 2026, and it matters because coding agents increasingly ship inside desktop shells with always-on telemetry.
TL;DR
| Question | Answer |
|---|---|
| Symptom | UI skeletons, dead clicks, streaming replies frozen mid-token |
| Root mechanism (author) | Unread Cache-Control: no-store bodies stay open in Mojo pipes → FD exhaustion |
| Triggering traffic | Claude Code presence clear POST with keepalive: true, .catch(() => {}) |
| Response payload | 27 bytes JSON refresh hint |
| Electron / Chromium | Claude Desktop Electron 44 ≈ Chrome 152 in the traced build |
| Datadog SDK | Bundled; not the sole holder of pipes per later repro |
| Fix status | Public Nov 14–15, 2026 post; vendor ticket disputed |
Why empty logs mislead
Main-process logs showed only “Main webview is unresponsive, will kill and reload” on a loop. Reload often failed. The renderer sometimes exited with FATAL: Crashing because hung, but stderr went to /dev/null until the author redirected it.
That pattern matches other Claude Desktop reliability threads where users blame the model when the shell is the bottleneck. Here the shell lost the ability to talk to its browser process because IPC backlog met descriptor starvation.
A rounded tank with green drops falling steadily from a small crack, echoing a slow descriptor leak
The presence request that nobody reads
On the Code tab, Claude Desktop sends presence telemetry so the server knows a client is active. When the window is hidden or idle, it sends a clear:
POST /v1/code/channels/<id>/client/presence
{ "client_id": "...", "clear": true }
The author reports this variant uses:
fetch(url, { method: "POST", headers, body, keepalive: true }).catch(() => {})
So the client never awaits or reads the response body. The regular presence heartbeat does call await r.json(), per the post. Only the clear path is fire-and-forget.
The server’s reply is tiny but not empty: {"refresh_after_seconds":0} — 27 bytes.
Chromium, no-store, and why garbage collection is not enough
When Blink hands a response body to the renderer, it normally wraps it in BufferingBytesConsumer so chunks drain quickly. fetch manager skips that wrapper when the response carries Cache-Control: no-store (or when integrity checking is enabled).
For unread no-store bodies, data stays in the Mojo data pipe. Each pipe uses a 2 MiB shared-memory region and consumes one file descriptor in the renderer.
Two pipeline endpoints joined by a green adapter, echoing how a no-store fetch keeps a Chromium data pipe open
The author’s hung dump showed 942 such regions, 938 containing only the refresh JSON. Meanwhile TransferBuffer::Initialize() failed spammed the journal — classic FD limit pressure.
Initial suspicion fell on Datadog’s browser SDK, which records completed fetches in a Set capped at 1000 entries. Unread no-store responses never get resource timing events, so they linger. A later minimal repro without retaining Response objects still leaked 836 descriptors after 800 requests, proving Chromium does not close pipes on GC.
That nuance matters for security teams: you cannot fix this class of bug only by dropping telemetry vendor retention. The fetch caller must consume or cancel the body, or the browser must release the pipe on object destruction.
Timeline the author measured
| Phase | Observation |
|---|---|
| Days 1–11 | Random renderer death; capped core dumps useless |
| Day 13 | Clean exit treated as intentional → blank UI until manual restart |
| Day 21+ | Verbose Chromium logging; GPU buffer failures mixed in |
| 35-hour uptime session | Streaming reply frozen at “6s · 246 tokens”; manual gcore while hung |
A fresh renderer accumulated ~45 leaked SHM regions per hour from presence clears alone in the author’s test. Extrapolation explains multi-day uptimes crossing hundreds of pipes without a restart.
A protected key inside a capsule beside a closed folder, echoing kernel file descriptors the renderer never releases
Electron memory dumps are not “1.5 TB of RAM”
Readers on Hacker News often misread sparse core files. Chrome 152 reserves about 1 TiB for the V8 pointer sandbox plus large guard regions so corrupted indices cannot escape. gcore reports virtual size, not resident pages. The author’s Rust parser used SEEK_HOLE / SEEK_DATA to search only mapped regions in under a second.
That technique is reusable for any Chromium hang where you cannot attach a debugger live.
What developers can do now
Mitigations (application side):
- Restart Claude Desktop on a schedule if you keep week-long sessions on Linux.
- Watch FD usage: sudden
TransferBuffererrors in journald around Claude processes. - Patch locally if you maintain a fork: read or
response.body.cancel()on presence clear, even when JSON is useless. - Raise
ulimit -nonly as a band-aid; pipes still cost kernel bookkeeping.
Mitigations (platform side):
- Track Electron upgrades — the author notes newer Chrome builds may change pipe lifecycle (verify per release).
- For internal Electron apps, audit
keepalive+ no-store combinations the same way you audit memory leaks.
Security posture: This is a denial-of-use bug, not remote code execution. It still matters in SOC workflows where analysts leave Claude Desktop open across shifts. Our desktop restriction guide covers containment patterns; this post adds reliability pressure on the same surface.
For coding-agent harness design, compare Claude Code CPU fixes and Cursor incident traces: desktop agents generate continuous side traffic; ignoring 27-byte replies is cheap until it is not.
What Chromium and Anthropic reportedly said
The author filed upstream. Chromium reportedly responded that behavior matches spec intent: unread no-store bodies should not be buffered in memory indefinitely, so the pipe stays open until consumed — consistent with the Fetch living standard guidance on suspending downloads.
The write-up also cites Chromium issue 342779913: upstream relanded a fix in Chrome 154 so Mojo fails more predictably under FD exhaustion instead of hanging silently. Claude Desktop’s traced build sits on Electron 44 / Chrome 152, so Linux users may not get that behavior until the shell upgrades.
An Anthropic ticket apparently auto-closed as not Claude Code, despite the stack trace originating in the Code tab presence client. Whether that categorization changes after the public post is unknown.
Builders should treat vendor triage labels as process noise when the reproduction crosses Electron + bundled observability SDKs + feature-specific fetch.
Minimal lesson for agent shipping teams
If your product sends heartbeat POSTs from an embedded webview:
- Always drain or cancel bodies, even for “meaningless” JSON.
- Do not assume
.catchon fetch releases kernel resources. - Load-test FD counts, not just heap snapshots.
- Separate telemetry SDK retention from browser networking contracts.
The same lesson applies to MCP-connected desktops and Bedrock-attributed IDE spend: the billing line item is visible; the descriptor leak is not, until the UI dies mid-task.
What developers are saying
The Hacker News thread was thin at crawl time (5 points, 1 comment), so there is little independent reproduction yet. When more Linux users confirm FD growth on lsof -p <renderer>, add numbers here — the author invited minimal repro without Claude binaries.
What people are asking about the freeze
Is this the same bug as high CPU in Claude Code CLI? No. explainx.ai covered Bun GC idle CPU as a separate runtime issue. The deafbeef trace is renderer networking state, not model inference cost.
Does restarting fix it permanently? Restart clears FD tables until presence clears accumulate again. For multi-day sessions, periodic restarts or a body-drain patch are the practical options the author describes.
Can I see it without a core dump? The post suggests watching open FD count on the renderer PID and journal lines like TransferBuffer::Initialize() failed. If those climb while idle, you are likely in the same failure class even before the UI freezes.
Should enterprises block Claude Desktop on Linux? That is a risk call. The failure is local availability, not remote exploitation. Security teams may still care because frozen agent sessions strand uncommitted work and encourage unsafe copy-paste recovery. Pair monitoring with the sandbox restrictions guide.
How fetch and keepalive interact here
The author highlights keepalive: true on the clear request. Keepalive lets the browser finish a request after navigation or teardown, which is useful for beacon-style telemetry. Combined with unread bodies, it extends pipe lifetime across task boundaries.
For application authors, the safe pattern is:
const response = await fetch(url, { method: "POST", headers, body, keepalive: true });
await response.body?.cancel();
Even when JSON is worthless, cancellation releases the pipe. Alternatively await response.arrayBuffer() on a 27-byte payload is cheap insurance.
Chromium’s no-store branch exists so sensitive bodies do not linger in memory. The trade-off is kernel resources until consumption. Agent products that poll every few minutes multiply that trade-off across workdays.
Monkey patches the author shipped before the root cause
Before the core dump, deafbeef layered local fixes on Claude Desktop: cap systemd-coredump size, reload the webview on clean-exit renderer deaths, and stream Chromium verbose logs into journald. Each patch reduced downtime but masked the underlying descriptor curve. That journey is familiar to anyone who maintained Electron chat clients during the early Slack desktop era: symptomatic reload loops until someone counts handles.
If you maintain an internal fork, consider feature-flagging presence clear during investigations, or route clears through a single shared worker that enforces body cancellation. Either approach beats asking users to tolerate minute-long UI freezes during long agent runs.
Related reading
- Restrict Claude Desktop with sandbox patterns
- Claude Cage: VM isolation for Claude Desktop
- Claude Code Projects proactive coordinator
- Exploit.cat on Claude Desktop Chromium patch gap
- Cursor embedded-browser flaw round-up
- What is MCP?
Electron version numbers, presence endpoint paths, and Datadog SDK behavior reflect deafbeef’s public post accessed 15 November 2026. Claude Desktop builds change frequently — confirm against your installed version.
