[BUG] Remote Control sessions die after ~20 min idle — server TTL ignores keepalives

Status Open
Maintainer reply None cached
Activity 19 comments · opened Mar 10, 2026

Environment

  • Claude Code: v2.1.72
  • OS: macOS (Darwin 25.2.0)
  • Plan: Max
  • Reproduced on: interactive CLI sessions (/remote-control), auto-RC (remoteControlAtStartup: true), and agent sessions (--agent)

Description

All Remote Control sessions silently die after ~5-30 minutes of idle time (most commonly ~20 minutes). The phone app shows "Failed to send message — An unknown network error has occurred" while the local CLI still displays "Remote Control active." The session_ingress endpoint returns HTTP 404 — the session is deregistered server-side while the CLI process is alive, connected, and polling.

This affects every RC user who steps away from their keyboard — the core use case of "start at your desk, continue from your couch."

Root Cause (source-verified against cli.js v2.1.72)

We identified two independent bugs that together leave idle RC sessions unprotected:

Bug 1: Server-side session TTL does not reset on keepalive messages

The WebSocket transport sends {"type":"keep_alive"} data frames every 5 minutes via startKeepaliveInterval() (300,000ms interval, Swz=300000). The WebSocket ping/pong mechanism also runs every 10 seconds (hwz=10000).

Neither mechanism prevents server-side session deregistration. We verified this empirically:

  • Sessions with the 5-minute keepalive actively sending keep_alive frames still die at ~20 minutes
  • Sessions with 10-second WebSocket pings running continuously still die at ~20 minutes
  • The server-side session TTL appears to only reset on real user/model activity (actual messages through the bridge), not on transport-level keepalive frames

Note on CLAUDE_CODE_REMOTE: When set (e.g., in standalone claude remote-control bridge mode), CLAUDE_CODE_REMOTE disables the 5-minute keepalive entirely via an early return in startKeepaliveInterval(). However, even when the keepalive IS running (interactive /remote-control sessions), sessions still die — confirming the server ignores these frames for TTL purposes.

// startKeepaliveInterval() — 5-min keepalive (runs for interactive sessions)
startKeepaliveInterval() {
  this.stopKeepaliveInterval();
  if (t6(process.env.CLAUDE_CODE_REMOTE)) return;  // skipped in standalone bridge mode
  this.keepAliveInterval = setInterval(() => {
    this.ws.send(JSON.stringify({type: "keep_alive"}) + "\n");
  }, 300000);  // 5 minutes
}

Bug 2: SEND_KEEPALIVES replacement is broken by refcount gating

CLAUDE_CODE_REMOTE_SEND_KEEPALIVES was designed as an additional keepalive mechanism that sends {"type":"keep_alive"} every 30 seconds through the transport. However, the keepalive interval is gated by a refcount (C36) that tracks active model processing:

// Refcount increment — called when tool execution or streaming starts
function DD1() {
  C36++;
  if (C36 === 1) ie7();  // start 30s keepalive interval
}

// Refcount decrement — called when tool execution or streaming ends
function XD1() {
  C36--;
  if (C36 === 0) {
    if (Dd !== null) { clearInterval(Dd); Dd = null; }  // ← CLEARS the keepalive interval
    kE9();  // start idle timer (informational only)
  }
}

// The 30s keepalive interval
function ie7() {
  if (Dd !== null) { clearInterval(Dd); Dd = null; }  // clear previous
  if (mg6 !== null) { clearTimeout(mg6); mg6 = null; }  // clear idle timer
  Dd = setInterval(() => {
    if (t6(process.env.CLAUDE_CODE_REMOTE_SEND_KEEPALIVES))
      I36?.();  // sends {"type":"keep_alive"} via registered callback
  }, 30000);  // 30 seconds
}

Visual summary of all three keepalive paths:

[RC Keepalive Architecture — 3 mechanisms, all broken during idle]
<img width="1800" height="1360" alt="Image" src="https://github.com/user-attachments/assets/dfc8b00f-40e0-47bb-8780-2e4d24420ddf" />

The flow:

  1. Model starts processing → DD1() increments C36 to 1 → ie7() starts the 30s keepalive interval
  2. Model finishes processing → XD1() decrements C36 to 0 → clearInterval(Dd) kills the keepalive
  3. Session is now idle with the SEND_KEEPALIVES mechanism stopped

The keepalive only runs while the model is actively processing — exactly when it's NOT needed. It stops during idle — exactly when sessions die.

We verified this empirically: sessions spawned with CLAUDE_CODE_REMOTE_SEND_KEEPALIVES=1 still died at ~25-30 minutes.

Bridge heartbeat — DISABLED server-side

// Server response from tengu_bridge_poll_interval_config:
{
  "poll_interval_ms_not_at_capacity": 2000,
  "poll_interval_ms_at_capacity": 600000,
  "heartbeat_interval_ms": 0
}

The bridge-level heartbeat infrastructure exists in the code (heartbeatWork() API call, poll loop heartbeat mode) but is server-disabled (heartbeat_interval_ms: 0).

Reproduction

  1. Start Claude Code: claude
  2. Enable RC: /remote-control
  3. Connect from Claude iOS/Android app
  4. Send one message to confirm connectivity
  5. Leave both sides completely idle
  6. Wait ~20 minutes
  7. Try sending a message from the phone → "Failed to send message — An unknown network error has occurred"
  8. The CLI still shows "Remote Control active"

Reproduction rate: 100% across 7 independent sessions tested (both relay agent sessions and normal interactive sessions).

Timeline observations:

| Session | Type | Age at death |
|---------|------|-------------|
| Interactive (auto-RC) | remoteControlAtStartup | ~21 min |
| Agent relay #1 | --agent with /remote-control | ~20 min |
| Agent relay #2 | --agent with /remote-control | ~20 min |
| Agent relay #3 | --agent with SEND_KEEPALIVES=1 | ~25 min |
| Agent relay #4 | --agent with SEND_KEEPALIVES=1 | ~30 min |
| Agent relay #5 | --agent with /remote-control | ~25 min |
| Agent relay #6 | --agent with /remote-control | ~25 min |

Control test: 75 messages in 5 minutes to a fresh session — survived fine. Message volume does not cause the drop. It is purely time-based idle death.

Additional evidence: Curling the session_ingress endpoint directly returns HTTP 404 while the local CLI process is alive with TCP ESTABLISHED connections. The server deregisters the session before TCP teardown — the CLI never detects the loss.

Suggested Fix

Bug 1 (server-side): The server should count keep_alive messages (or WebSocket pings) as session activity for TTL purposes. Alternatively, enable heartbeat_interval_ms server-side (set to e.g. 30000) — the client-side heartbeat infrastructure already exists and runs unconditionally.

Bug 2 (client-side, one-line fix): Don't clear the keepalive interval when SEND_KEEPALIVES is set. In XD1(), skip clearing Dd when CLAUDE_CODE_REMOTE_SEND_KEEPALIVES is truthy:

function XD1() {
  C36--;
  if (C36 === 0) {
    if (!t6(process.env.CLAUDE_CODE_REMOTE_SEND_KEEPALIVES)) {
      if (Dd !== null) { clearInterval(Dd); Dd = null; }
    }
    kE9();
  }
}

This would make the 30-second keepalive run continuously during idle. Combined with Bug 1's fix (server counts keepalives as activity), idle sessions would survive indefinitely.

Current Workaround

The only effective mitigation is triggering periodic real model activity (e.g., sending a trivial message via terminal input every ~15 minutes). This resets the server-side session TTL because it generates actual messages through the bridge transport.

We also patched cli.js to restore the 5-minute keepalive (removing the CLAUDE_CODE_REMOTE guard from startKeepaliveInterval) — sessions still died at ~20 minutes, confirming the server-side TTL is the primary bug. Client-side keepalive fixes alone are insufficient.

Impact

This affects every Remote Control user. The core marketing promise — "start a task at your desk, then pick it up from your phone on the couch" — breaks the moment you set your phone down for 20 minutes.

Related Issues

These all describe symptoms of this root cause:

  • #28571 — Session fails to resync after connection drop
  • #28914 — Connection dies overnight while tmux stays alive
  • #29313 — Sessions go stale mid-conversation
  • #28532 — Frequent disconnections requiring page refresh
  • #29726 — iOS app drops on background/foreground
  • #28402 — Session not visible in session list
  • #29219 — Connection fails after rate limit
  • #32651, #32746, #32833 — Recent reports of shorter (~5-15 min) idle death windows

Supporting downstream evidence: Henderson11 on #28571 traced WebSocket close code 1002 (protocol error) occurring after the disconnect — this is the downstream effect of the session expiring server-side. Our analysis identifies the upstream cause: the server-side TTL that ignores keepalive traffic, compounded by the SEND_KEEPALIVES refcount bug that disables the only mechanism intended to address this.

Research Methodology

  • Static analysis of /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js (v2.1.72, 12MB minified)
  • Function-chain tracing through minified code: DD1XD1C36ie7I36keep_alive
  • Empirical testing: 7 sessions across 4 configurations (interactive, auto-RC, agent relay, SEND_KEEPALIVES=1)
  • Verified SEND_KEEPALIVES=1 doesn't fix idle death (refcount drops to 0 between turns)
  • Verified cli.js patch removing CLAUDE_CODE_REMOTE guard doesn't fix idle death (server ignores transport-level keepalives for TTL)
  • Verified on normal interactive session (remoteControlAtStartup) — not specific to agent/relay usage
  • Cross-referenced with 10+ existing GitHub issues (all symptom reports, no root cause analysis)

View original on GitHub ↗

19 Comments

github-actions[bot] · 5 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/31853
  2. https://github.com/anthropics/claude-code/issues/32833
  3. https://github.com/anthropics/claude-code/issues/28914

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

sidkandan · 5 months ago

This is not a duplicate. The linked issues (#31853, #32833, #28914) are symptom reports — they describe that sessions die but not why.

This issue identifies two specific code-level bugs in the keepalive system with source-verified root cause analysis:

  1. Server ignores keepalive frames for TTL: The 5-min WS keepalive (startKeepaliveInterval, Swz=300000) and 10s WS ping (hwz=10000) are both active, but the server-side session registry does not count them as activity. Empirically verified — sessions with keepalives actively sending still die at ~20 min.
  1. SEND_KEEPALIVES refcount gating: The 30s application keepalive is gated by a refcount (C36) that tracks active model processing. DD1() starts the interval when a tool/stream begins; XD1() calls clearInterval() when it ends. The keepalive only runs during active model turns — not during idle, which is exactly when sessions die. Empirically verified — sessions with CLAUDE_CODE_REMOTE_SEND_KEEPALIVES=1 still die at ~25-30 min.
  1. Bridge heartbeat disabled server-side: heartbeat_interval_ms: 0 in the tengu_bridge_poll_interval_config response. The infrastructure exists but is turned off.

None of the linked issues contain this analysis. They have zero Anthropic responses despite 40+ combined upvotes and months of reports across 10+ duplicate issues.

Three concrete fixes with code are provided in the issue body.

sidkandan · 5 months ago

UPDATE: This appears to be fixed in v2.1.74! 🎉

Traced through cli.js in v2.1.74 and found a new 4th keepalive mechanism that wasn't present in v2.1.72:

session_keepalive_interval_ms — 2-minute session keepalive

// Default config:
qi = {..., session_keepalive_interval_ms: 120000}

// In remote-io session setup:
let $ = R16().session_keepalive_interval_ms;
if ($ > 0) this.keepAliveTimer = setInterval(() => {
  this.write({type: "keep_alive"}).catch(...)
}, $)

Key properties:

  • NOT gated by CLAUDE_CODE_REMOTE (unlike mechanism #1 in the original report)
  • NOT refcount-gated (unlike mechanism #2)
  • ✅ Fires every 120s regardless of model activity
  • ✅ Present in both the remote-io layer AND the bridge repl layer

Test results: RC session survived 30+ minutes idle on v2.1.74 with zero intervention. Previously failed 100% of the time at ~20 min on v2.1.72.

The 3 original mechanisms from this report are unchanged — the WS keepalive still returns early for RC, SEND_KEEPALIVES is still refcount-gated, heartbeat_interval_ms is still 0. The fix was adding a clean new mechanism that bypasses all three issues. Smart approach.

---

Incredible turnaround by the team — shipped within a day. @noahzweben, @ashwin-ant, and the rest of the Claude Code team — thank you for the fast response. This makes Remote Control genuinely usable for the "code from your couch" workflow.

Happy to close this issue if the fix is confirmed intentional. For anyone else hitting this — update to v2.1.74.

rosstex · 5 months ago

Found this from your reddit thread, kudos for not giving up on fixing this!

Naam · 5 months ago

I don't think it's been solved. I'm still
unable to connect from my remote sessions. I have a service that tries keeping alive a session on a repo on a particular machine for me to work on when I'm not in front of the computer, but by the time I need it. It always hangs. Suggesting leaving open for now?

roscoe1981 · 5 months ago

Remote sessions (started on claude.ai/code) die then can't be recovered

validator-99uptime-algo · 5 months ago

2.1.87 (Claude Code)
Linux Ubuntu. It does not work after 20 minutes etc... Useless.

Chi-Brian · 3 months ago

Still happening all the time to me. Running ubuntu VM that has 0 downtime. Remote connections always disconnect.

YonganZhang · 3 months ago

Same on Linux (Ubuntu 22.04, Claude Code on a shared HK server, Pro plan). Suggesting platform:linux label since the current platform:macos undercounts coverage.

One additional behavioral detail not yet documented in this thread — the desktop/mobile split:

  • Desktop agent viewer: the dead session is still listed with full metadata. Right-click → reanimate works, and the conversation history is fully restored. So locally, this is "annoying but recoverable."
  • Mobile remote app: the same session appears in the session list, but its state is shown as disconnected (greyed out, "lost connection" indicator). There is no way to reactivate it from mobile — the user has to walk back to the desktop, reanimate it there, and only then does mobile re-establish the bridge.

This breaks the documented "start at your desk, continue from your couch" promise even for users who do eventually return to their desk — the mobile side is effectively read-only for any session that crossed the ~20 min TTL boundary.

batrashish-yahoo · 3 months ago

Any ETA to fix it?

sippykup2 · 2 months ago

Come on. Pretty please...?

Ashkaan · 2 months ago

Confirming this still bites on v2.1.150 (self-hosted claude remote-control under systemd), and I traced exactly why for my account.

The v2.1.74 fix (the 4th keepalive, session_keepalive_interval_v2_ms, code default 120000, not refcount-gated) is present in my binary. The problem is the value resolution: it comes from the GrowthBook flag tengu_bridge_poll_interval_config, and the server pushes session_keepalive_interval_v2_ms: 0 to my account. That trips the if (M > 0) guard, so the keepalive timer never starts and RC sessions archive after ~15 min idle.

Resolver in v2.1.150:

let M = oLH().session_keepalive_interval_v2_ms;
if (this.isBridge && M > 0) this.keepAliveTimer = setInterval(() => this.write({ type: "keep_alive" }), M);

oLH() reads the GrowthBook flag with the code default { ..., session_keepalive_interval_v2_ms: 120000 }. My cached value is 0, and it gets rewritten to 0 on every flag refresh (I set it to 30000 locally, a single client start reset it back to 0).

What does not work as a user-side fix:

  1. Editing the cached value in ~/.claude.json. Overwritten on the next GrowthBook refresh.
  2. DISABLE_GROWTHBOOK=1. This does make the resolver fall back to the 120000 default (v$ returns the passed default when GrowthBook is off), but Remote Control itself is GrowthBook-gated, so the host then exits with Error: Remote Control is not yet enabled for your account. The keepalive and the RC-enable gate are coupled to the same switch.
  3. A per-flag override. The two override layers checked before the GrowthBook lookup (qf$ / Kf$) are not wired to any input in this build, so there is no surgical way to force just this one flag.

Net result: for any account where the server sends session_keepalive_interval_v2_ms: 0, RC is enabled but unusable past the idle timeout, with no client-side workaround.

Could the team either flip this flag's value for affected accounts, or decouple the RC-enable gate from DISABLE_GROWTHBOOK so the code default keepalive can be used when GrowthBook is off? Happy to share more of the trace if useful.

jihwan-dw · 2 months ago

This is still an issue.

paisanllc · 1 month ago

Another data point confirming @Ashkaan's diagnosis that this is now an account-level flag rollout problem, not a client bug — verified on v2.1.220.

Environment: Claude Code v2.1.220, macOS (Darwin 25.4.0), Max plan.

Server-pushed flag values (cached tengu_bridge_poll_interval_config in ~/.claude.json for my account):

{
  "session_keepalive_interval_ms": 0,
  "session_keepalive_interval_v2_ms": 0,
  "heartbeat_interval_ms": 0
}

Binary check: the v2.1.220 binary still contains the v2.1.74 fix with its schema default intact —

session_keepalive_interval_v2_ms: E.number().int().min(0).default(120000)

— and the same guarded resolver Ashkaan traced in v2.1.150:

let g = ngt().session_keepalive_interval_v2_ms;
if (this.isBridge && g > 0) this.keepAliveTimer = setInterval(() => { ... this.write({type: "keep_alive"}) ... }

So for any account served 0, the keepalive timer never starts, every other keepalive path is also disabled (heartbeat_interval_ms: 0, plus the two mechanisms from the original report), and RC sessions are deregistered server-side after ~15–20 min idle. Symptoms match the original report exactly: mobile shows "Failed to send message — An unknown network error has occurred", the CLI still shows "Remote Control active", and the dead session cannot be revived from mobile.

As Ashkaan noted, there's no client-side workaround: editing the cached value is overwritten on the next GrowthBook refresh, and DISABLE_GROWTHBOOK=1 disables the RC-enable gate itself.

Request: please flip session_keepalive_interval_v2_ms to a positive value for affected accounts (the code default of 120000 already works — v2.1.74 proved that for accounts that receive it), or decouple the RC-enable gate from GrowthBook so the code default can apply.

sgsallhands · 13 days ago

Still present in v2.1.233 — the refcount gate is unchanged, and the client now volunteers its idleness to the server

Confirming this on v2.1.233 (macOS, Darwin 25.6.0, Max plan, interactive /remote-control sessions). The original analysis was against v2.1.72; both bugs survive ~160 releases later, and reading the current binary turned up two things that sharpen the report.

1. The refcount gate in Bug 2 is unchanged in v2.1.233

Same shape, different minified names:

var l6d = 30000;                                   // 30s keepalive interval

function d6d(e) {                                  // start
  Hoa(e);
  e.heartbeatTimer = setInterval((t) => {
    xr("debug", "session_keepalive_heartbeat", { refcount: t.refcount });
    if (V.CLAUDE_CODE_REMOTE_SEND_KEEPALIVES) t.activityCallback?.();
  }, l6d, e);
}

function p6d(e) {                                  // increment path
  let t = u6d(); if (!t) return;
  t.activityCallback = e;
  if (t.refcount > 0 && t.heartbeatTimer === null) d6d(t);
}

// decrement path
if (r.refcount === 0 && r.heartbeatTimer !== null)
  clearInterval(r.heartbeatTimer), r.heartbeatTimer = null, Zwb(r);

So the keepalive interval still exists only while a turn is running, and even then only sends when CLAUDE_CODE_REMOTE_SEND_KEEPALIVES is set. An idle session — the entire population this issue is about — has no keepalive timer at all.

The telemetry event name session_keepalive_heartbeat carries refcount as its only attribute, which should make the gating visible in your own telemetry: for sessions that later die of idleness, that event stops before the death rather than continuing at 30s.

2. The client actively advertises its idleness, so this is not just "unprotected" — it is opted into

Every heartbeat carries an idle_seconds the client computes itself:

function pai(e) {
  let t = e?.isTurnRunning ?? (() => iJt() > 0);   // iJt() = main-loop refcount
  let r = ys(), n = Date.now();
  return {
    noteActivity() { n = Date.now(); r.emit(); },
    sampleIdleSeconds() {
      let o = Date.now();
      if (t()) { n = o; return 0; }                 // turn running ⇒ idle 0
      return Math.max(0, Math.floor((o - n) / 1000));
    },
    onActivity: r.subscribe
  };
}

sent as:

await this.request("post", "/worker/heartbeat", {
  session_id: this.sessionId,
  worker_epoch: this.workerEpoch,
  ...this.advertiseHeartbeatProbeSupport && {
    supports_heartbeat_probe: true,
    current_interval_seconds: Math.round(this.heartbeatIntervalMs / 1000),
    ...(idleSeconds !== undefined && { idle_seconds: idleSeconds })
  }
}, "Heartbeat", ...)

The server answers with an idle grant the client latches (idleGrantLatched, surfaced through isIdleAdvised()), and the heartbeat interval stretches accordingly.

idle_seconds is derived purely from turn activity — there is no user-presence, terminal, or viewer input to it. A session deliberately parked waiting for a remote prompt is indistinguishable, on the wire, from one that has been abandoned. That is the crux: the one thing Remote Control exists to support (walk away from the desk, drive it from a phone) is the exact state the heartbeat protocol reports as "nothing happening here".

Suggested fixes, in preference order

  1. idle_seconds should be min(turn_idle, remote_presence_idle) — a session with a live Remote Control registration is never idle for TTL purposes, regardless of whether a turn is running.
  2. Failing that, ungate the 30s keepalive from the refcount: run it whenever a bridge/replHandle is attached. The refcount was the wrong lifetime to hang it on; a keepalive whose job is to survive idleness cannot itself be conditioned on non-idleness.
  3. CLAUDE_CODE_REMOTE_SEND_KEEPALIVES should default on for interactive Remote Control sessions. As shipped it is a flag that only takes effect in the state where it is unnecessary.

A diagnostic note for anyone landing here from a tmux report

tmux is a red herring, and it is costing people debugging time. I checked v2.1.233 for any tmux-attachment awareness and there is none: zero occurrences of list-clients, session_attached, or client_attached; the only focusOut symbols in the binary come from a bundled web UI library, so terminal focus events are wired to nothing; and the heartbeat is a plain setTimeout chain that node runs regardless of whether a terminal is rendering. A detached tmux pane still owns its PTY and nothing is suspended.

tmux appears in every one of these reports (#28914 and its duplicates) simply because tmux is how people leave a session idle. On my machine the correlation was perfect and entirely spurious: the one session that dropped was the only one that sat idle, while sibling sessions in the same tmux server, under the same detach events, ran autonomous multi-hour tasks and never dropped once.

Cross-references

  • #28914 (closed NOT_PLANNED by the stale bot, then locked) is the same idle-death, reported by four users across Ubuntu, WSL2 and a Mac Mini, all through tmux. It is the earliest clear statement of the symptom and was closed for inactivity rather than addressed.
  • #34255 is the other half of the user-visible failure — I've posted the circuit-breaker mechanism there. Combined with this issue, an idle drop becomes a permanent one after three occurrences in an hour, which is why nothing ever recovers on its own.
  • #33041 carries evidence of server-side environment cleanup dropping even non-idle sessions — a third, independent cause that a fix here should not be assumed to cover.
ImagineTheGames · 12 days ago

Win11/Max20 plan/v.1.30096.1/remote control at startup(true):
Happens to me on Windows11 both with powershell and claude desktop app constantly. Steps to reproduce are to just turn on a /remote-control session and eventually it will disconnect after a few hours (even thought the host computer is on and the session is still there in host computer)...

What's the point of setting up a remote-control if I have to be back at my terminal to turn it on every now and again...

velsa · 12 days ago

The failure isn't really RC — it's that the session's lifetime is chained to one machine's uptime. If your network/wifi blips, or the host sleeps → the session dies and RC has nothing to reconnect to.

In order to fix this for myself, I had to invert the setup: my claude session runs on a small always-on box (a VPS I own), and every device — laptop, phone — is just a client. I simply use claude desktop/mobile apps and they see the remote claude's session 24/7. Since then "silently died" hasn't happened once, because nothing the session depends on ever sleeps.

Disclosure: I ended up building tooling around this pattern, so I'm biased — but the pattern works with plain tmux + SSH too.

ImagineTheGames · 4 days ago

I've noticed now in sessions started directly in windows 11 powershell RC persists... its been at least 1 week and nothing has gone down. but for sessions started on the claude desktop app on windows 11 RC dies after a few hours... the fix is to just not use the desktop app on the actual host that needs to keep RC on... you can still use it the desktop app remotely, that doesn't affect anything

bautrey · 4 days ago

Confirming on v2.1.246, with an environment that rules out sleep and network as contributing causes

Adding a data point that isolates the server-side TTL as sufficient on its own, since an earlier comment suggested the trigger may be network blips or host sleep. On this machine neither can be a factor:

| Factor | Value |
|---|---|
| Host | Mac Studio (always-on desktop, never mobile) |
| pmset | sleep 0, displaysleep 0, disksleep 0, standby 0 — sleep fully disabled |
| Network | Wired Ethernet (en0), UniFi gateway |
| Link flaps | 0 over a 6-hour window (log show on link state) |
| Claude Code | v2.1.246 (thread's most recent confirmation was v2.1.233) |
| Plan | Max |

No sleep. No wifi. No link transitions. Sessions still die on idle. Whatever else may compound it, the server-side TTL reproduces this in a completely static environment.

Scale

11 concurrent named interactive sessions, each launched with --remote-control <name> and remoteControlAtStartup: true:

claude --name FP-Gateway --remote-control FP-Gateway --chrome --resume --model opus
claude --name FP-Talent  --remote-control FP-Talent  --chrome --resume --model opus
... (9 more)

Attention rotates across them, so most sit idle for long stretches by design. Every one of them produces could not reach the Remote Control server for about 30 minutes and requires a manual /remote-control.

The part that makes this more than a nuisance

Idle is not an edge case for Remote Control. It is the default state of the use case. Nobody enables RC to supervise a session they are sitting in front of; they enable it because they are about to walk away. A TTL that only tolerates presence breaks the single scenario the feature exists to serve.

Concretely: start a long task, leave, have a 30-minute conversation somewhere, pull out your phone to check on it, and the session is unreachable with no way to recover it from the phone. Reconnection requires physical access to the desktop, which is precisely what you did not have. The practical workflow that survives today is "stay at your desk, keep poking the session," which is the opposite of remote control.

That timing is what turns a reconnect bug into a design problem: the failure lands exactly when the user is furthest from the machine and least able to fix it. Worth weighting when this is prioritized against a server-side change.