[Bug]: "Tailscale HTTPS" says "Start Tailscale" while Tailscale is running (CLI not on the app's PATH, macOS)#15919

opened28d agobyKkevvykevwin

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. macOS with the Tailscale app running and the Mac connected to the tailnet (tailscale status from the app's bundled CLI lists it).
  2. Launch T3 Code from the Dock/Finder and open the desktop settings for the local environment.
  3. Look at the "Tailscale HTTPS" row.

Expected behavior

The row detects Tailscale and offers HTTPS through Tailscale Serve / MagicDNS.

Actual behavior

The row says "Start Tailscale to set up HTTPS access through MagicDNS." even though Tailscale is running. The "Tailscale IP" address is still listed under Network access, so only the HTTPS option is affected.

From the bundle, the server runs a bare tailscale status --json. The macOS Tailscale app keeps its CLI at /Applications/Tailscale.app/Contents/MacOS/Tailscale, and tailscale is not on the server's PATH, which for an app launched from the Dock is /usr/bin:/bin:/usr/sbin:/sbin. I believe that is why the lookup fails, but I did not capture the spawn error itself.

Suggestion: on macOS also try the app bundle path, and/or show "Tailscale CLI not found" instead of "Start Tailscale" when the spawn fails.

Impact

Minor bug or inconvenience

Version or commit

Desktop Alpha 0.0.45

Environment

macOS (Darwin 25.6.0), Apple Silicon

Synced from GitHub

I hit this on current Nightly (0.1.4-nightly.20261027.3796) and tracked down the root cause. It isn't (only) PATH: even when the app finds the CLI, the Tailscale binary runs in GUI mode because the Dock-launched app's env has no SHLVL/TERM.

Repro (no T3 needed), simulating a Finder/Dock-launched process:

sh
1env -i PATH=/usr/bin:/bin HOME=$HOME /Applications/Tailscale.app/Contents/MacOS/Tailscale status --json; echo "exit=$?"
2# The Tailscale GUI failed to start: The operation couldn’t be completed. (Tailscale.CLIError error 3.)
3# exit=0
4
5env -i PATH=/usr/bin:/bin HOME=$HOME TAILSCALE_BE_CLI=1 /Applications/Tailscale.app/Contents/MacOS/Tailscale status --json | head -c 40
6# { "Version": "1.104.x-…", "TUN": true, …
Env passed to the Tailscale app binaryResult
none of the below (launchd / Finder / Dock)GUI failure text on stdout, exit 0
SHLVL set (any value, even 0)CLI JSON
TERM setCLI JSON
TAILSCALE_BE_CLI setCLI JSON

This matches the mode check described in #21609 (ppid, then TAILSCALE_BE_CLI / SHLVL / TERM / TERM_PROGRAM / PS1).

What happens in T3: DesktopShellEnvironment copies only an allowlist from the login shell (PATH, SSH_AUTH_SOCK, HOMEBREW_*…), so with the Tailscale app dir on the login-shell PATH, the desktop does find the binary, but spawns it without SHLVL/TERM. tailscale status --json exits 0 with that non-JSON text, parseTailscaleStatus fails, and orElseSucceed(() => null) turns it into "no Tailscale" with nothing logged. getAdvertisedEndpoints() then has tailscale-ip: but no tailscale-magicdns:.

So the bundle-path suggestion above wouldn't fix it on its own. I checked: spawning the absolute .../MacOS/Tailscale path fails the same way. Command name, path case, PATH lookup vs absolute path, detached, stdin, cwd and output size make no difference (288-run matrix across plain Node, ELECTRON_RUN_AS_NODE, Electron main, and Electron launched via open).

Reproduces on both Tailscale variants:

  • macOS 27.0.1 arm64, Tailscale 1.104.1, App Store (io.tailscale.ipn.macos)
  • macOS 27.2 arm64, Tailscale 1.104.2, standalone (io.tailscale.ipn.macsys)

Workaround: a wrapper earlier on PATH than the app dir:

sh
1#!/bin/sh
2TAILSCALE_BE_CLI=1 exec /Applications/Tailscale.app/Contents/MacOS/Tailscale "$@"

(A plain sh wrapper also works because sh sets SHLVL. A symlink does not: in CLI mode it aborts with BundleIdentifiers.swift:49: Fatal error: The current bundleIdentifier is unknown to the registry.)

Suggested fix: add TAILSCALE_BE_CLI=1 with extendEnv: true to every darwin tailscale spawn in packages/tailscale (status, debug prefs, serve), and log a warning (e.g. tagged gui-mode) instead of silently returning null when the status can't be parsed. That also covers t3 serve started by launchd. I have a small patch with tests that passes vp run check:changed. Happy to open a PR if you want one.

Co-authored with Claude Opus 5.5 in Claude Code.