DEV Community

Kaushik N
Kaushik N

Posted on

Tailscale said Connected. My app said it couldn't reach my PC. They were both right

My app is a small web app on my phone that starts Claude Code sessions on my Windows PC. The phone reaches the PC over Tailscale: tailscale serve gives the PC an HTTPS address on my private network and forwards requests to a Node server listening only on 127.0.0.1.

It had worked for weeks. Then, in one afternoon, it broke three different ways, and every one of them looked like a bug in my app. None of them were.

Break 1: "Cannot reach the agent"

The phone app opened, asked for my passcode, and told me to keep retrying.

The server was fine. It answered locally in two milliseconds. So I asked Tailscale what it was forwarding:

> tailscale serve status
No serve config
Enter fullscreen mode Exit fullscreen mode

The forwarding had quietly disappeared. I never found out why. --bg is meant to survive reboots. Turning it back on was one line:

tailscale serve --bg --https=8790 8790
Enter fullscreen mode Exit fullscreen mode

The phone connected straight away.

Break 2: a white screen, then "the server stopped responding"

Next I added the app to my home screen, from Safari and from Chrome. Both opened to a blank white page for a minute or two, then Safari gave up: the server stopped responding.

A browser tab on the same phone worked. That made it look like a home-screen app problem, and I nearly went digging into service workers.

The clue was in my own terminal. Right after turning serve back on, my first test request from the PC had timed out, and the second one came back in 26 milliseconds. Tailscale issues the HTTPS certificate on the first connection after serve is enabled, and that connection can take tens of seconds. The home-screen apps had hit that window.

By the time I'd set up a way to watch it, the window had passed and both apps loaded fine. So this one is my best explanation rather than a proof. But the fix costs nothing either way: my setup script now makes one request itself, right after enabling serve, before it hands you the link. The script waits for the certificate, not your phone.

Break 3: "Waiting for the PC", while Tailscale said Connected

An hour later the app hung on its lock screen: Waiting for the PC. The Tailscale app on the phone said Connected. The server was up. The HTTPS address answered from the PC in 30 milliseconds.

I'd been guessing for long enough. I needed to see what the phone actually sent. Tailscale keeps no request log I could read, so I put a recorder in the middle: a tiny Node relay between tailscale serve and my server, logging when each request starts and when it finishes.

http.createServer((req, res) => {
  const id = ++n, t0 = Date.now();
  log(`#${id} > ${req.method} ${req.url.split('?')[0]}`);
  const up = http.request({ host: '127.0.0.1', port: 8790, method: req.method,
                            path: req.url, headers: req.headers }, (ur) => {
    res.writeHead(ur.statusCode, ur.headers);
    ur.pipe(res);
    ur.on('end', () => log(`#${id} < ${ur.statusCode} ${Date.now() - t0}ms`));
  });
  req.on('aborted', () => log(`#${id} ! client aborted`));
  req.pipe(up);
}).listen(8792, '127.0.0.1');
Enter fullscreen mode Exit fullscreen mode

Then I pointed serve at the relay instead of the server. The phone keeps the same address, so nothing changes on its side:

tailscale serve --bg --https=8790 http://127.0.0.1:8792
Enter fullscreen mode Exit fullscreen mode

(It deliberately logs only the method and path. Headers carry the session token, and a debugging log is the last place that should end up.)

The log settled it in one screen. The phone fetched every file of the app, and each was answered in under 50 milliseconds. Then it made the call that checks the passcode, /api/auth/status, and that request never arrived. Not once, across 45 seconds of retries.

So the tunnel worked for some requests and not others. From the PC, a Tailscale ping to the phone had one timeout, then a 560 ms reply, where it had been 17 ms that morning. And when I asked what the phone's Tailscale app itself said, it had a warning: magicsock is not running. That's the part of Tailscale that actually moves your traffic.

Fully quitting and reopening the Tailscale app fixed it. Seconds later the log showed the passcode check arrive, then the unlock, then my project list.

What I changed

  • The setup script now warms the certificate itself before handing over the link.
  • The README has an FAQ entry for this exact state: stuck on Waiting for the PC or Can't reach your PC while Tailscale says Connected means quit and reopen Tailscale on the phone.
  • The relay went back in a drawer. It took about a minute to put in and a minute to take out, which is the right price for a tool you only need on a bad day.

What I took away

  • "Connected" is a status, not proof that traffic is moving. Measure the thing you actually care about.
  • When two sides disagree, put a recorder at the boundary before touching either side. One screen of log beat an hour of theories.
  • The first request after turning on HTTPS pays for the certificate. Make your own tooling pay it, not your users.

This is part of Claude Remote, which starts Claude Code on your own Windows PC from your phone. The repo goes publi

Top comments (0)