anthropics / anthropics/claude-code
[BUG] Desktop SSH session + Remote Control: while the laptop sleeps, claude.ai connectors are unusable from the phone because they execute inside the sleeping Desktop app — answering the PreToolUse permission hook is not enough
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Preflight Checklist
- [x] I have searched existing issues (closest open: #35637, #78784, #49790; #67840 was bot-closed — see Related issues)
- [x] This is a single bug report
- [x] Reproduced on the Claude Code build Claude Desktop ships for SSH sessions, `ccd-cli` 2.1.260 (live-tested 2026-09-16; also seen on 2.1.255/2.1.258)
### What's Wrong?
A Claude Code session hosted by **Claude Desktop over SSH**, with **Remote Control enabled**, stays drivable from the phone after the laptop sleeps — prompts, `Bash`, `Read`, and `AskUserQuestion` all work, because the CLI itself hosts Remote Control on the remote host. But **claude.ai connector calls (Google Drive, Notion, Gmail, calendar, Linear, etc.) cannot complete**, and this is structural, not just a timeout:
**These connectors execute inside the Desktop app, not on the remote host.** Desktop delivers them to the SSH session as SDK-hosted MCP servers (they appear in the session's `initialize` frame under `sdkMcpServers`). The remote CLI does not run them; it calls back into the Desktop app to run each tool. When the laptop sleeps, that app is unreachable, so the connector's actual work cannot run — regardless of Remote Control being alive and the remote CLI being healthy.
There are two layers, and **fixing only the first does not fix the bug**:
1. **The PreToolUse permission hook times out.** Every tool Desktop registers a PreToolUse hook for (on 2.1.260: all MCP tools via a `mcp__.*` matcher, plus `Workflow`, `SendMessage`, and a set of `mcp__ccd_*` session tools) fires a `hook_callback` the sleeping laptop can't answer. Desktop supplies no per-hook `timeout`, so the CLI's 600 000 ms default applies and the call fails at exactly 600 s with `PreToolUse hook did not respond before its timeout (host client may be unreachable)`. No prompt reaches the phone, because the failure is one stage before the permission prompt.
2. **Even with the hook answered, the connector still can't run.** I verified this directly (see below): I answered the stuck PreToolUse hook in ~20 s with a local shim. The call then proceeded, a permission prompt **did** reach the phone, I approved it — and the connector call **still hung**, because executing it requires the sleeping Desktop app. So the missing per-host-unreachable hook timeout is real, but it is not the whole bug. The connector-hosting layer is.
### Evidence that answering the hook is insufficient
To isolate the two layers I built a small stdio shim between the `claude-ssh` daemon and the remote CLI that watches the SDK stream and injects an empty "no opinion" `control_response` for a Desktop PreToolUse `hook_callback` once it has been pending 20 s (it never touches `can_use_tool`, device hooks, timed matchers, or non-PreToolUse events). Live test, 2026-09-16, laptop asleep, driven from the phone, calling Google Drive `list_recent_files`:
```
14:41:02 pending cb=hook_2 (PreToolUse mcp__…__list_recent_files) ← hook fired; sleeping host can't answer
14:41:23 answered by shim ← shim answered it after 21 s
→ permission prompt then appeared on the phone; I approved it
→ the connector call still hung: its execution needs the awake Desktop app
```
So: the hook can be answered server-side in seconds instead of 600 s, and that is enough to surface the permission prompt on the phone — but it is **not** enough to run a Desktop-hosted connector. The session's `initialize` confirms the connectors are SDK-hosted: `sdkMcpServers` lists the connector server ids (Drive, Notion, calendar, Gmail, Linear, etc.), i.e. Desktop runs them, not the remote CLI.
### Mechanism (from the shipped binaries, logs, and live SDK stream)
1. The `claude-ssh` daemon idle-closes a bridge connection after ~5 min with no traffic (`internal/server.(*Server).closeWhenIdle`; log `[Server] closing connection idle %s in both directions`). Awake, Desktop reopens it lazily; asleep, the open-bridge count sits at **0** for the whole sleep.
2. Desktop registers hooks via the SDK `initialize` request (`hooks: { PreToolUse: [{ matcher, hookCallbackIds, timeout? }] }`). A matching tool makes the CLI send a `hook_callback` control request back over stdio → daemon → bridge → SSH → Desktop. With no bridge, nothing answers.
3. No per-hook `timeout` is supplied, so the CLI default applies (`ccd-cli` 2.1.260): `let ut = Se.timeout ? Se.timeout*1000 : bf; var bf = 600000`; on timeout it stops the tool with `PreToolUse hook did not respond before its timeout (host client may be unreachable)`.
4. **The observed PreToolUse matchers on 2.1.260** (from a live `initialize`): `mcp__.*` (all MCP tools); a list of specific `mcp__ccd_directory/session_mgmt/scheduled-tasks/sidebar/pr/*` tools plus `^SendMessage$`/`^ListAgents$`/`^ListPeers$`; and `^Workflow$`. `Edit`, `Write`, and `Bash` matched **no** PreToolUse hook on this version, so they are not gated by it and complete normally while asleep (earlier host transcripts did show `Write`/`Edit` failing at 600 s, so the registered matcher set has changed across versions; the connector facet is version-independent because it is about where the connector runs).
### What Should Happen?
With Remote Control connected, a connector call should either run to completion or fail fast with a clear "Desktop client asleep/unreachable" state — not hang. More fundamentally: a session advertised as usable from the phone should not depend on the laptop staying awake to run its connectors. Either host the connectors somewhere durable (server-side / CLI-connected for SSH sessions), or make it explicit that connectors require the awake host.
### Steps to Reproduce
1. In Claude Desktop, start a session on an SSH host. Enable Remote Control; confirm the session appears in the iOS app.
2. Close the laptop; wait ≥5 min so the daemon idle-closes the bridge.
3. From the phone, send a prompt (works), ask for a `Bash` command (works), trigger `AskUserQuestion` (renders, answerable).
4. From the phone, call a claude.ai connector (Drive/Notion/Gmail/calendar/Linear). It hangs; no prompt appears for 600 s, then it fails with the error below (or, if the permission hook is somehow answered, it surfaces a prompt and then still hangs on execution).
5. Open the laptop; the bridge reconnects within seconds and the call completes.
### Error Messages/Logs
```
PreToolUse hook did not respond before its timeout (host client may be unreachable).
The tool call was not executed; other configured hooks may not have completed.
```
Daemon log `~/.claude/remote/run//remote-server.log` (open-bridge count in brackets):
```
2026/09/08 15:01:14 [Server] New connection from: @ [1]
2026/09/08 15:37:14 [Server] closing connection idle 5m4s in both directions: @ [0]
<- 1 h 40 min with zero bridge connections
2026/09/08 17:17:55 [Server] New connection from: @ [1] laptop woken
```
Across host transcripts, 23 genuine `entrypoint: claude-desktop` events failed at ~600 s (17 MCP/connector calls; earlier versions also 4 `Write` + 2 `Edit`). `Bash`/`Read`/`ToolSearch`/`AskUserQuestion`: 0 failures.
### Claude Model
Opus / Fable (both observed).
### Is this a regression?
No — the connector-hosting dependency and the 600 s hook timeout both predate current versions (#67840 shows the same 600 s failure on 2.1.170).
### Claude Code Version
`ccd-cli` 2.1.260 shipped by Claude Desktop for SSH sessions (also 2.1.255/2.1.258). Latest CLI at time of writing is 2.1.273; nothing in the 2.1.257→2.1.273 changelog addresses hook-callback timeouts, bridge idle-close, or connector hosting.
### Platform / OS
Anthropic API. Remote host: Ubuntu 22.04, Linux 5.15 (SLURM compute node reached via an SSH login proxy). Client: Claude Desktop on macOS, steered from the Claude iOS app.
### Related issues
- **#35637** (open) — permission prompts not rendering on mobile; later comments report the same ~600 s phantom-timeout signature on Desktop/remote sessions. No maintainer reply; most recent comment: "Remote control is unusable because of this."
- **#67840** (bot-closed, not by a maintainer; flagged as an incorrect closure per #87647) — same 600 000 ms mechanism on 2.1.170.
- **#78784**, **#49790** (open feature requests) — durable/reattachable remote sessions. These would keep the *CLI process* alive across disconnects but would **not** relocate connector execution off the Desktop app, so they would not fix this facet on their own.
- **#71875** (bot-closed) — keep the host awake via OS power APIs; a sidestep, not a fix.
- **#92687**, **#91034** — partial reattach and a per-reconnect process leak; adjacent, not this.
### Suggested fixes (in priority order for the connector facet)
1. **Host claude.ai connectors server-side for SSH sessions** (CLI-connected rather than SDK-hosted in Desktop), so a connector's execution does not depend on the laptop being awake. This is the only change that makes connectors usable from the phone while the laptop sleeps.
2. When the SDK host is unreachable, fail (or hold) the PreToolUse hook callback in seconds instead of 600 s, and route the permission decision to connected Remote Control clients. (Necessary, but insufficient alone — see the shim evidence.)
3. Surface a clear "Desktop client asleep/unreachable" state on the mobile client instead of a silent 600 s hang.
4. Don't idle-close the bridge (or make the 5-minute daemon timeout configurable) for sessions with Remote Control on, and reconnect proactively on wake.
### Workaround (partial)
Hosting the session with `claude remote-control` inside `tmux` on the remote host avoids the Desktop hook and the laptop dependency for **server-side** work (edits, Bash, and any CLI-connected MCP servers), and stayed up 7+ hours across a sleep. It does **not** give you the Desktop-hosted claude.ai connectors, because those are only delivered by the Desktop app — the same reason they fail while asleep. So there is currently no way to use the claude.ai connectors from the phone while the laptop sleeps.
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce the SSH Remote Control session with the laptop asleep, then inspect the session initialize data for sdkMcpServers and the daemon's remote-server.log. Confirm whether connector calls depend on the sleeping Desktop bridge and whether PreToolUse callbacks reach the 600-second timeout. Done means connectors either execute without the Desktop host or fail quickly with a clear unreachable-host state.
Written by the indexing model from the issue text.
Assessment
- Domain
- backend-api-design, cli, networking
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100