spawn/packages
Audrey Sage Lorberfeld 2221f1155b
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run
fix(cursor-proxy): macOS local cursor CLI compatibility (#3426)
## Problem
`spawn cursor local` fails on macOS with four cascading errors:

```bash
chmod: /usr/local/bin/caddy: No such file or directory
bash: line 4: caddy: command not found
bash: /etc/hosts: Permission denied
bash: line 24: setsid: command not found
```

These occur in packages/cli/src/shared/cursor-proxy.ts (setupCursorProxy and startCursorProxy). Even though local/agents.ts wires runLocal as the runServer callback, the proxy scripts it constructs were written exclusively for Linux:

1. Caddy downloads the wrong binary — `setupCursorProxy` calls `https://caddyserver.com/api/download?os=linux&arch=amd64` unconditionally. On macOS this downloads a Linux ELF binary that immediately fails to execute.
2. Caddy install path is root-owned on macOS — the script writes to `/usr/local/bin/caddy`. On macOS this directory either doesn't exist (no Homebrew) or is owned by a system process, so `chmod +x` immediately fails without sudo.
3. `/etc/hosts` is read-only without root on macOS — the domain-spoofing step runs echo `"127.0.0.1 ..." >> /etc/hosts` directly, which fails with Permission denied because `/etc/hosts` is only writable by root on macOS (unlike some Linux setups where the invoking user may own it).
4. `setsid` is Linux-only — the non-systemd fallback in startCursorProxy calls `setsid` to detach backend processes. `setsid` is a util-linux command; it doesn't ship on macOS (or BSD, or Windows). `nohup … &` is the portable equivalent.
The downstream symptom of all four: Caddy never starts, the proxy is never listening, and Cursor's auth exchange fails — which surfaces as "The provided API key is invalid" even when the key is valid.

## Fix
- OS/arch detection — query `process.platform` and `process.arch` (already available in Bun) to select the correct Caddy download (os=darwin, arch=arm64 | amd64 for macOS; os=linux for Linux).
- User-writable install path — install Caddy to `~/.local/bin/caddy` (guaranteed writable, always exists after `mkdir -p`) instead of `/usr/local/bin/`. Add `~/.local/bin` to `PATH` in the execution environment.
- `sudo` for `/etc/hosts` — prefix the `sed` and `echo` host entries with `sudo`. Wrap in a try/catch with a clear fallback message if `sudo` isn't available (some sandboxed environments).
- Replace `setsid` with `nohup` — swap `setsid $NODE ...` for `nohup $NODE ... < /dev/null >> /tmp/cursor-proxy.log 2>&1 &` in the non-systemd branch. `nohup` is POSIX and present on macOS, Linux, and BSD.

## Affected code
- `packages/cli/src/shared/cursor-proxy.ts` — `installCaddy` shell fragment, `hosts-spoofing` fragment, `startCursorProxy` non-systemd branch
- No changes to the CloudRunner interface, tests, or other agents

## Testing
Tested on macOS (Apple Silicon, M3) with SPAWN_CLI_DIR pointing at a local build:
- `spawn cursor local` completes all setup steps without errors
- Caddy starts on port 443, unary backend on 18644, bidi backend on 18645
- Cursor authenticates and routes completions through OpenRouter
2026-05-21 16:23:37 -07:00
..
cli fix(cursor-proxy): macOS local cursor CLI compatibility (#3426) 2026-05-21 16:23:37 -07:00
shared fix: rethrow normalized Error in tryCatchIf/asyncTryCatchIf (#2930) 2026-03-23 19:33:05 -07:00