Remote control: sessions die permanently on session-limit hit and on transient disconnects; mobile app still shows them alive and silently drops messages

Status Open
Reported on v2.1.212
Maintainer reply None cached
Activity 3 comments · opened Jul 22, 2026

Summary

Two compounding defects make remote-control sessions die permanently and silently. The mobile app keeps listing them as alive, so messages sent to them are accepted by the UI and never answered.

Environment: Claude Code 2.1.212, macOS 25.5.0 (Darwin), remote-control via mobile app.

---

Defect 1 — sessions never resume after a session limit resets

On 2026-07-22 at 15:22:40 (+03), three concurrently active remote-control sessions all received:

You've hit your session limit · resets 10:30pm (Asia/Riyadh)

The limit window reset at 22:30. At 23:30 — one full hour after reset — all three sessions were still dead, with zero activity since 15:24. Nothing re-arms them. The limit is temporary; the session death is permanent.

Two aggravating details:

  1. The limit notice is written into the transcript as an assistant message. From the mobile app this renders as the conversation having replied, so there is no visible signal that the session is now a dead end. A user message sent at 15:23:25 got only this banner back.
  2. Background task notifications kept being delivered into the dead sessions (e.g. 15:25:50) and each one re-emitted the same banner — scheduled/background work continues firing into a session that can no longer act.

Defect 2 — a transient network blip tears down all active sessions

Separately, a recoverable disconnect causes claude remote-control to back off exponentially and then shut down every active session — failing even to stop them cleanly, because the network is down by definition:

·|· Reconnecting · retrying in 2.2s · disconnected 1s
·/· Reconnecting · retrying in 3.8s · disconnected 3s
[18:26:00] Shutting down 5 active session(s)…
[18:26:03] Error: Failed to stop work cse_<redacted> after 3 attempts:
           getaddrinfo ENOTFOUND api.anthropic.com

Measured across ~2 days of ~/.local/state/remote-control/child.log: 5 teardown events killing 14 sessions total (1 + 5 + 1 + 3 + 4). Some disconnects were as short as 48 seconds.

The two defects compound: the three sessions killed by the limit at 15:22 were then formally torn down by this path at 18:26.

Impact

From the phone, a conversation looks alive and accepts input, but nothing is listening. There is no error, no notification, and no indication that the session is unrecoverable. Long-running delegated work is lost silently.

This is very likely the same root cause as #79827 (duplicate device registrations / stale environments) — those stale environments are the residue of these uncleaned teardowns.

Requested behavior

  1. Do not permanently kill a session on a session-limit hit — re-arm it when the limit window resets, or surface an explicit, actionable dead state.
  2. Do not tear down active sessions on a recoverable disconnect; the existing backoff already handles short outages.
  3. Propagate session death to the client, so the mobile app stops presenting dead sessions as live and stops silently swallowing messages sent into them.

View original on GitHub ↗

3 Comments

markolofsen · 1 month ago

I’d separate the durable task from the remote-control session. The session is a disposable worker/lease: it can hit a quota, lose its network, or be replaced without making the task disappear.

For each task, keep a small state record outside the transcript: task id and monotonic revision, last accepted artifact, pending approval, lease owner and expiry, retry state, and an explicit terminal/dead reason. A reconnect should resume from that bounded state and replay missed revisions; it should not depend on the old conversation still being alive.

When a session becomes unavailable, the client should stop presenting it as alive and route new messages to the task inbox (or reject them visibly), rather than accepting input into a dead transcript. Background notifications should be idempotent projections of task state, so retries or quota resets cannot create duplicate or misleading messages. That turns a transient disconnect into “reconnect and resume task X,” while a real terminal failure remains visible and actionable.

I build cmdop, so I’m biased — we’re working on the same machines-and-agent-sessions problem with outbound-only connectivity and a browser messenger. The useful boundary for us is that the machine remains the execution authority while the remote client is only the conversation surface.

nschneir · 1 month ago

Confirming this on macOS with Claude Code 2.1.220 (i.e. still present eight releases after the 2.1.212 report).

gervallarta · 23 days ago

Adding a data point from the desktop app that overlaps with this issue: on top of sessions dying on transient drops, the desktop app also deliberately disconnects idle sessions — and the mobile app renders both cases identically as "Disconnected", so users can't tell a recoverable pause from a permanently dead session.

Environment

  • Claude desktop app 1.26832.0 (bundled claude-code 2.1.222), macOS 26.5.2
  • Sessions created and running locally in the desktop app; observed from the iOS app's Code tab

What the logs show (~/Library/Logs/Claude/main.log)

Every session gets a 15-minute idle timer, re-armed on each interaction:

[WarmLifecycle:session] Starting idle timeout for local_<id>: 900s
[WarmLifecycle:preview] Starting idle timeout for local_<id>: 1800s

When it expires, the app disconnects the session on purpose — this repeats every 15 min all day for any idle session:

09:48:32 [info] [WarmLifecycle:session] Idle timeout reached, disconnecting local_64a392f3...
10:03:32 [info] [WarmLifecycle:session] Idle timeout reached, disconnecting local_64a392f3...
10:18:32 [info] [WarmLifecycle:session] Idle timeout reached, disconnecting local_64a392f3...

The 900s value is hardcoded in the app bundle with no user-facing setting:

this.warmLifecycle = new K.Y({ idleTimeoutMs: 900*1e3, timeoutOnHidden: !0,
  onDisconnect: e => this.pauseSession(e), onWarmUp: e => this.warmSession(e), ... })

Related lifecycle behaviors in the same logs: all sessions stop on app quit ([CCD] Stopping 3 active session(s) on quit), and a governor can evict idle sessions under memory pressure ([CliGovernor] memory pressure (warning): would evict N idle session(s)).

Why this matters for this issue

  1. On mobile, a deliberately paused (recoverable) session and a permanently dead one (the transient-disconnect case this issue describes) are indistinguishable — both just say "Disconnected". Users assume something broke.
  2. There is no way to re-warm a paused desktop session from the mobile app; you have to touch it on the desktop first. Combined with the reconnect fragility described here, the mobile experience is that desktop sessions "randomly" go dark after ~15 minutes, every time.

Requests

  • Distinct states in the mobile UI ("Paused — resumes on activity" vs actually dead/unrecoverable).
  • Re-warm on remote attach: opening a paused session from mobile should wake it, like interacting on desktop does.
  • Ideally, make idleTimeoutMs configurable.