Skip to content

Commit 6653aa7

Browse files
andyhtranclaude
andauthored
Fix Tailscale peer discovery in notarized builds (#2)
The bundled Tailscale binary detects whether to run as CLI or GUI based on context. From a notarized app's subprocess (no TTY) it relaunches the GUI instead of returning JSON, so peer discovery silently returned an empty list. Setting TAILSCALE_BE_CLI=1 forces CLI mode. See tailscale/tailscale#16063 and #7140. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f757a84 commit 6653aa7

2 files changed

Lines changed: 11 additions & 3 deletions

File tree

Sources/CopyCat/Broadcast.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ struct ShellResult: Sendable {
196196
/// Runs a process with stdin detached, capturing stdout and stderr separately
197197
/// so callers can distinguish "no output" from "failed silently". Returns
198198
/// `nil` only when the process couldn't be spawned at all (bad path, etc).
199-
func runShell(_ exec: String, args: [String]) -> ShellResult? {
199+
func runShell(_ exec: String, args: [String], env: [String: String]? = nil) -> ShellResult? {
200200
let p = Process()
201201
p.executableURL = URL(fileURLWithPath: exec)
202202
p.arguments = args
203+
if let env { p.environment = env }
203204
let outPipe = Pipe()
204205
let errPipe = Pipe()
205206
p.standardOutput = outPipe

Sources/CopyCat/TailscaleDiscovery.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ enum TailscaleDiscovery {
2424
// hosts the user can pick from.
2525
static func allPeers() -> [TailscalePeer] {
2626
guard let bin = executablePath else { return [] }
27-
guard let result = runShell(bin, args: ["status", "--json"]) else {
27+
// TAILSCALE_BE_CLI=1: when the bundled Tailscale binary is invoked from
28+
// a notarized app's subprocess (no TTY), it relaunches the GUI instead
29+
// of running as CLI. The env var forces CLI mode. See
30+
// tailscale/tailscale#16063 and #7140.
31+
let env = ["TAILSCALE_BE_CLI": "1", "PATH": "/usr/bin:/bin"]
32+
guard let result = runShell(bin, args: ["status", "--json"], env: env) else {
2833
Log.app.error("tailscale status: spawn failed for \(bin)")
2934
return []
3035
}
@@ -36,7 +41,9 @@ enum TailscaleDiscovery {
3641
Log.app.info("tailscale status exit=\(result.exitCode): \(stderr)")
3742
return []
3843
}
39-
return parseTailscalePeers(json: result.stdout)
44+
let peers = parseTailscalePeers(json: result.stdout)
45+
Log.app.info("tailscale status ok: \(peers.count) peers via \(bin)")
46+
return peers
4047
}
4148

4249
static func onlineHostnames() -> [String] {

0 commit comments

Comments
 (0)