fix(tailscale): fall back to the macOS app binary when tailscale isn't on PATH - #5145
fix(tailscale): fall back to the macOS app binary when tailscale isn't on PATH#5145quinnypig wants to merge 1 commit into
Conversation
…t on PATH Tailscale's macOS app doesn't add `tailscale` to PATH; users typically work around this with a shell alias to the app's bundled binary. That alias is invisible to our direct (non-shell) subprocess spawn, so `readTailscaleStatus`/`ensureTailscaleServe`/`disableTailscaleServe` fail with ENOENT and Tailnet detection silently reports "not on Tailscale" even when Tailscale is running. When spawning `tailscale` fails with ENOENT on darwin, retry once against the app's bundled CLI at /Applications/Tailscale.app/Contents/MacOS/Tailscale if that file exists. No change in behavior on other platforms or when the app binary isn't present.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Approved e17cca1 Straightforward macOS bug fix that adds a fallback to the Tailscale.app binary when You can customize Macroscope's approvability policy. Learn more. |
What changed
packages/tailscale/src/tailscale.ts: when spawningtailscalefails withENOENTon macOS, retry once against/Applications/Tailscale.app/Contents/MacOS/Tailscaleif that file exists, instead of surfacing the spawn failure immediately. This applies to bothreadTailscaleStatus(Tailnet/MagicDNS detection) andrunTailscaleCommand(serve/serve off). The file-existence check is exposed as an injectableTailscaleExecutableFileCheck(mirrors the existingClaudeExecutableFileCheckpattern inapps/server/src/provider/Drivers/ClaudeExecutable.ts) so tests can fake the filesystem instead of touching real paths. Added three tests covering the retry, the no-retry-when-binary-absent case, and the no-retry-on-non-macOS case. No behavior change on Linux/Windows or when the app binary isn't installed.Why
Tailscale's macOS app does not put a
tailscalebinary onPATH. Their own docs have users work around this with a shell alias (alias tailscale=/Applications/Tailscale.app/Contents/MacOS/Tailscale) or by manually symlinking it into/usr/local/bin. A shell alias only exists inside an interactive shell — it's invisible tochild_process.spawn, which does a directPATHlookup without going through a shell. So on any Mac where the user relies on the alias (rather than a manual symlink), every tailscale spawn in this codebase fails withENOENT, and that failure is currently swallowed as "not on Tailscale" (e.g.apps/desktop/src/backend/tailscaleEndpointProvider.tstreats the read failure asmagicDnsName: null). The user's machine is actually on the Tailnet andtailscale status --jsonworks fine when typed at a prompt — this is purely a subprocess PATH-resolution gap, not a networking issue.I hit this locally:
tailscaleresolved viatype tailscaleas a zsh alias only, and a barechild_process.spawn('tailscale', ...)reproducedSPAWN ERROR: spawn tailscale ENOENTeven thoughtailscale status --jsonsucceeded when run directly in the shell.Test plan
pnpm --filter @t3tools/tailscale typecheckpnpm --filter @t3tools/tailscale test(16/16 passing, including 3 new cases)Note
Low Risk
Scoped to macOS subprocess resolution with existence-gated fallback; no change to Linux/Windows or when the app binary is absent, and tests cover the new paths.
Overview
On macOS, when spawning
tailscalefails with ENOENT, the package now retries once using/Applications/Tailscale.app/Contents/MacOS/Tailscaleif that file exists. This covers status reads and serve / serve off commands, so Tailnet detection works when users rely on a shell alias instead of a PATH binary.File existence is checked via an injectable
TailscaleExecutableFileCheck(defaultstatSync), so tests can fake the filesystem. Linux, Windows, and macOS without the app binary keep the previous behavior—no fallback off darwin or when the app executable is missing.Three tests cover successful fallback, missing app binary, and no fallback on non-macOS.
Reviewed by Cursor Bugbot for commit e17cca1. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fall back to the macOS Tailscale.app binary when
tailscaleis not on PATHtailscalefails with aNotFounderror on macOS,readTailscaleStatusandrunTailscaleCommandnow retry using/Applications/Tailscale.app/Contents/MacOS/Tailscaleif that file exists.TailscaleExecutableFileCheckEffect context reference wraps the file-existence check, allowing tests to inject a mock without touching the filesystem.NotFounderror is preserved and surfaced as aTailscaleCommandSpawnError.Macroscope summarized e17cca1.