Remote control: desktop device bridge keepalive starves when app is occluded/locked (macOS) — pong timeout every 11–16 min, device reads persistently offline

Status Fixed / completed
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 31, 2026 · closed Aug 25, 2026

Summary

The Claude Code remote-control device bridge hosted inside the macOS Claude desktop app fails its WebSocket keepalive every ~11–16 min whenever the app is occluded (e.g. Mac at the lock screen). Each failure logs pong timeout — resetting, followed by an immediate successful reconnect + authenticated — but the server marks the device offline during the gap and is slow to re-mark it online, so mobile / claude.ai surfaces read the machine as persistently "offline" even though every local CLI session is healthy and bridge-registered.

Environment

  • macOS 27.0 (26A5388g), Apple Silicon (M4 Pro)
  • Claude desktop: 1.24012.9
  • Claude Code CLI: 2.1.220 — 4 interactive sessions running, all with bridgeSessionId, session-file heartbeats current throughout

Failing layer

The desktop-app device bridge: wss://bridge.claudeusercontent.com/devices/<org>_<device>/<host>/bridge, logged under [remote-tools-device] in ~/Library/Logs/Claude/main.log.

CLI sessions are not affected — ~/.claude/sessions/*.json shows all sessions alive with ticking mtimes during the flapping. Restarting CLI sessions does not help; relaunching the desktop app temporarily does.

Evidence

Log excerpt after a clean desktop relaunch at 18:48 (all times 2026-07-30, local; IDs/hostname redacted):

18:48:44 [remote-tools-device] authenticated
19:04:01 [remote-tools-device] pong timeout (1 consecutive) — resetting
19:04:02 [remote-tools-device] connecting wss://…/bridge
19:04:02 [remote-tools-device] authenticated
19:14:59 [remote-tools-device] pong timeout (1 consecutive) — resetting
19:15:00 [remote-tools-device] authenticated
19:30:47 [remote-tools-device] pong timeout (1 consecutive) — resetting
19:30:48 [remote-tools-device] authenticated

Cadence between pong timeouts: 15.3 min, 10.9 min, 15.8 min. During the same window all 4 CLI sessions kept operating normally (status: busy/idle, updatedAt ticking).

The log also shows benign ~30-min rotating (drain_wait_ms=0 inflight=0) events — clean drain + reconnect, a distinct (fine) mechanism.

Probable cause

The Mac was at the lock screen the whole time. A fully occluded Electron app gets its renderer/background timers throttled by macOS (App Nap / power management), so the bridge keepalive misses its pong deadline even though the network path is fine — CLI TCP connections to Anthropic hosts stay up for hours on the same machine at the same time.

Local mitigation being validated: defaults write com.anthropic.claudefordesktop NSAppSleepDisabled -bool YES + relaunch. Will report whether pong timeouts stop.

Secondary observation: a quit desktop app reads identically

Before the relaunch above, the desktop app had been quit at 2026-07-29 09:55 (Running onQuitCleanup: remote-tools-device-cleanupclose) and stayed dead for ~33 h. That entire window is indistinguishable from flapping on the mobile surface — the machine simply reads "offline". Because the device bridge lives only inside the desktop app, any quit/crash of the app is a full remote-control outage, with no signal surfaced anywhere; CLI sessions keep running and look fine locally.

Relation to #34868

Different layer and mechanism. #34868 covers the CLI's session_ingress WebSocket closed server-side (~25-min lifetime, 1006). This issue is the desktop app's device bridge failing its client-side keepalive (pong timeout). The server-side rotation appears in this log as the benign rotating events noted above.

Suggested fixes

  1. Run the bridge keepalive on a non-throttled path (main-process timer, or an NSActivity assertion while the bridge is connected) so occlusion/lock screen can't starve it.
  2. Re-mark the device online faster after a successful reconnect (shorter offline hysteresis), so a ~2-s flap doesn't produce a multi-minute offline reading.
  3. Surface "device bridge disconnected / desktop app not running" inside claude CLI sessions, so users can distinguish "sessions dead" from "bridge dead" without reading Electron logs.

View original on GitHub ↗