Kelly Michels 7 min read Debugging

Nine Tools Where There Were Thirty-Five

A Claude Code computer_unreachable postmortem — and a lesson in trusting the logs over the theory.

After a reinstall rescued the desktop app, every existing Code session started hanging for two minutes and then failing with "Remote Control host unreachable." New sessions worked. Mobile showed the sessions alive. The failure masqueraded as session orphaning for most of a day — until two log lines, one second apart, contradicted every theory I had.

Read the full, unedited session transcript →

The symptom

Every pre-existing Code session in the desktop app behaved the same way: type a prompt, watch it say "Sending…" for about forty seconds to two minutes, then get the banner — "Can't reach your computer," and under "View details," "Remote Control host unreachable (computer_unreachable)."

The failure, in full: a generic "can't reach your computer" banner whose real error string is computer_unreachable.

Two things made this strange. First, it was a local session — the app was running on the same machine I was typing on, so "remote" was already the wrong frame. Second, brand-new sessions worked perfectly. Only the old ones died.

A session running on the very machine at the keyboard, reported as a lost Remote Control connection.

The architecture: a cloud relay

The desktop app's Code tab is the claude.ai web UI running inside an Electron shell. It does not talk to the local session host directly — it reaches it through a cloud relay, a WebSocket bridge:

wss://bridge.claudeusercontent.com/devices/<device-uuid>_<account-uuid>/rez/bridge

So "Can't reach your computer" is the web side of that bridge failing to find your device. That reframes the whole problem: the device might be connecting just fine, and the failure could be entirely in what it advertises once connected. Which is exactly what the logs showed — the device connects and authenticates one second before the banner appears.

Forty seconds of "Sending," then the banner — the bridge waiting on a capability the device never advertised.

The evidence, in two log lines

The device logs the tool set it registers with the bridge each time it connects. Comparing two connections on the same day, same account, same unchanged device ID:

11:11:37  [remote-tools-device] connecting DO bridge with:
          computer_screenshot, computer_left_click, ...   (~35 tools)

13:10:38  [remote-tools-device] connecting DO bridge with:
          get_device_info, device_list_dir, ...           (9 tools, no computer_*)

There it is. Before the reinstall the device advertised roughly thirty-five tools, including the entire computer_* family. After it, it advertised nine — and every computer_* capability was gone. A session that needs those capabilities can never be served: the bridge waits, times out, and reports the host unreachable. A session that doesn't need them — a new one — connects and works. That single difference is why the failure looked, for most of a day, like the old sessions had been orphaned.

It is worth naming the theory this killed. The obvious story — the reinstall changed the device identity, so old sessions were bound to a dead device — was wrong. The device ID was identical across the reinstall. The sessions weren't bound to a dead device; they were asking a live device for tools it had stopped offering.

Why it was so hard to see

Several things conspired to keep the real cause hidden:

  • The device side logs nothing at the moment of failure. The connect and authenticate lines are there; the "why did this session get no tools" decision is not.
  • /remote-control hangs indefinitely in a desktop-hosted session, with no output — so the one command that sounds diagnostic tells you nothing.
  • DevTools is disabled in the packaged build. Ctrl+Shift+I is rebound to the model selector, so the web layer's actual error was only ever reachable through the banner's "View details."
Back on MSIX after the reinstall — same package family, config restored. Account identifiers are redacted.

A second bug, found en route

While chasing the first problem, the CLI suddenly refused to start, reporting that settings.json was malformed. It wasn't the settings file. Claude Code keys its per-project state in ~/.claude.json on the raw working-directory string. On a case-insensitive filesystem, one lowercase cd into a project that was first opened with different casing creates two keys that differ only in case:

"...\evomedia.net\smartplantehs\..."
"...\evomedia.net\SmartPlantEHS\..."

JSON treats those as distinct keys; Windows treats the paths as the same directory. The result is a file with duplicate keys — invalid JSON — after which the CLI's own parser rejects it and reports "settings.json is malformed." It reproduces on any Windows machine in about ten seconds, and the fix is as small as the bug: let tab-completion supply the on-disk casing rather than typing the path yourself.

Where the sessions actually live

The reassuring part: nothing was lost. The transcripts are plain .jsonl files under ~/.claude/projects — the file is the conversation, every turn and every tool call. They render on the mobile app (so they're intact server-side) and they resume in full from the terminal, which uses a different entry point that never touches the Remote Control bridge:

claude --resume <session-id>

So the honest status is: unfixed, but not lost. Desktop chat and new Code sessions work; the pre-reinstall sessions are reachable through the CLI while the tool- registration regression stands. When a "gone" session still opens with full history from the command line, the thing that broke was the pointer, not the data.

← Back to Blog