cd /news/artificial-intelligence/why-claude-desktop-on-linux-freezes-… · home › topics › artificial-intelligence › article
[ARTICLE · art-169951] src=explainx.ai ↗ pub= topic=artificial-intelligence verified=true sentiment=↓ negative

Why Claude Desktop on Linux Freezes: A 27-Byte Telemetry Reply and an FD Leak

Developer deafbeef traced freezes in Claude Desktop on Linux to unread `Cache-Control: no-store` fetch responses left open in Chromium Mojo data pipes, causing file-descriptor exhaustion, according to a public write-up posted November 14–15, 2026 on blog.dfb.sh. The triggering traffic is a Claude Code presence-clear POST sent with `keepalive: true` and `.catch(() => {})` that never reads the server's 27-byte `{"refresh_after_seconds":0}` reply; the author's hung-process dump showed 942 two-megabyte shared-memory regions, 938 holding only that JSON, alongside repeated `TransferBuffer::Initialize() failed` errors. A minimal repro without retaining Response objects still leaked 836 descriptors, and the author reports the vendor ticket is disputed; the post is not an official Anthropic publication.

by read8 min views1 publishedNov 15, 2026
Why Claude Desktop on Linux Freezes: A 27-Byte Telemetry Reply and an FD Leak
Image: Explainx (auto-discovered)

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) UnreadCache-Control: no-store bodies stay open in** Mojo pipes** →FD exhaustion
Triggering traffic Claude Codepresence** 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” ; manualgcore 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):

  1. Restart Claude Desktop on a schedule if you keep week-long sessions on Linux.
  2. Watch FD usage : sudden**TransferBuffer** errors in journald around Claude processes.
  3. Patch locally if you maintain a fork: read or**response.body.cancel()** on presence clear, even when JSON is useless.
  4. Raise ulimit -n only 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 auditmemory 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 .catch on fetch releases kernel resources.
  • Load-test FD counts , not justheap snapshots .
  • Separate telemetry SDK retention frombrowser 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.

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.

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @deafbeef 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/why-claude-desktop-o…] indexed:0 read:8min 2026-11-15 · —