Troubleshooting
Failures are surfaced, not swallowed. Two places tell you almost everything:
tail -f ~/.localrun/logs/daemon.log # the daemon's own log
ls ~/.localrun/logs/ # one pair of files per projectThe tray panel also carries an issues[] banner. If it is empty and something is still wrong, the
cause is usually one of the entries below.
macOS refuses to open the app
You see: On first launch, "LocalRun cannot be opened because the developer cannot be verified." Nothing appears in the menu bar.
Cause: The app is signed, but ad-hoc, not with a Developer ID, and not notarised. Gatekeeper has no developer identity to check against, so it blocks the first launch. The download is fine.
Fix: Right-click (or Control-click) LocalRun in Finder and choose Open, then confirm. macOS records the exception and every launch after that is normal. You can also allow it from System Settings → Privacy & Security, where the blocked app appears with an Open Anyway button immediately after a failed launch.
If you see "LocalRun is damaged and can't be opened" instead, with an offer to move it to the Bin, the quarantine flag needs clearing directly. This is the rarer case; some download paths and some macOS versions report it this way.
xattr -dr com.apple.quarantine /Applications/LocalRun.app.test addresses do not resolve at all
You see: curl: (6) Could not resolve host, or the browser searches the web for your project
name. Every project is affected, not one.
Cause: /etc/resolver/test names a port that nothing is listening on. The DNS responder binds
an unprivileged high port on loopback (15353 by default) and the resolver file has to name that
exact port. If the responder fell back to a different port, or the file was written by an older
install, the two disagree and every lookup goes nowhere.
Fix: Compare the file against the port the panel reports, then re-run Setup from the tray to
rewrite it. bootstrap.status diffs the two and reports stale when they differ.
cat /etc/resolver/test
# nameserver 127.0.0.1
# port 15353
dig +short @127.0.0.1 -p 15353 anything.test # should answer 127.0.0.1If dig answers on the port but the browser still does not resolve, the resolver file is the
problem, not the responder.
The first lookup after setup still fails
You see: Setup reports success, but the address does not work until some minutes later, or until a reboot.
Cause: macOS caches negative DNS results. The failed lookups from before setup are still cached.
Fix:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponderSetup runs this for you. Run it by hand if you edited /etc/resolver/test yourself.
The address resolves, but the page is blank
You see: An empty 200, or a Caddy default response, on every LocalRun address. The panel says
the proxy is up.
Cause: Another Caddy already owns port 80. Both processes bind it via SO_REUSEPORT, both look
healthy in lsof, and the other one wins every connection, so LocalRun serves nothing while
reporting itself as up.
Fix: LocalRun does not compete for the port. It writes its site blocks to
~/.localrun/caddy/localrun.caddy and adds a single import line to the system Caddyfile, then
reloads through the admin API, with no sudo. If that has not happened, check who holds the port and
whether the system Caddyfile carries the import line:
sudo lsof -nP -iTCP:80 -sTCP:LISTEN
# the Homebrew Caddyfile; adjust if yours lives elsewhere
grep -n localrun "$(brew --prefix)/etc/Caddyfile"Delete the import line to detach LocalRun from the system Caddy at any time.
The browser warns about the certificate
You see: NET::ERR_CERT_AUTHORITY_INVALID on an https://<name>.test address, even though the
tray shows HTTPS as configured.
Cause: Caddy's internal root is installed but not trusted. Those are different states, and only trust matters.
Fix: Check the one thing that decides it, then re-run Setup if it fails.
ROOT=~/Library/Application\ Support/Caddy/pki/authorities/local/root.crt
security verify-cert -c "$ROOT" -p sslCSSMERR_TP_NOT_TRUSTED means the certificate is sitting in your keychain with no trust settings.
Do not fix it with caddy trust; that targets the System keychain and its authorization dialog
cannot render from a background process. See HTTPS & certs for the full path.
Firefox uses its own trust store and needs security.enterprise_roots.enabled in about:config,
or a manual import.
A project runs in the tray but fails under launchd
You see: pnpm: command not found, bun: command not found, or a missing node in the project's log, but the same command works fine in your terminal.
Cause: A GUI app gets a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin), and launchd gives a
service the same. Homebrew, nvm, and Volta binaries are all invisible.
Fix: LocalRun rebuilds PATH for generated LaunchAgents: <cwd>/node_modules/.bin first, then
the well-known install directories. If your toolchain lives somewhere unusual, name it explicitly:
LOCALRUN_BIN_DIRS=/opt/custom/bin:/another/binA project whose run command still does not resolve shows a drift note before it fails.
A service will not stay stopped
You see: You stop something and it is running again seconds later.
Cause: It is supervised (by launchd, or by LocalRun's own always tier), and the supervisor is
doing its job.
Fix: Stop it through LocalRun, which routes to the supervisor rather than signalling the
process. Stopping an always project also demotes it to manual, so the next reboot does not
resurrect it. Never kill a supervised pid directly; launchd restarts it and you learn nothing.
launchctl list | grep dev.localrunOpen in Terminal does nothing, then a note appears
You see: The button appears to hang for about a minute, then a note mentions the Automation privacy pane.
Cause: Driving another app over AppleScript needs Automation permission. The first attempt
raises "LocalRun wants to control Terminal"; if nobody answers it, the call fails with
AppleEvent timed out (-1712) after 60 seconds.
Fix: Grant it in System Settings → Privacy & Security → Automation → LocalRun. The app is ad-hoc signed and macOS keys these grants to the signature, so the grant may not survive an app update; regrant it if the note comes back.
EADDRINUSE on the daemon socket, but no daemon is running
You see: The daemon refuses to start, claiming the address is in use, with nothing listening.
Cause: A unix socket path cannot exceed roughly 104 bytes. A LOCALRUN_DIR deep enough to push
daemon.sock past that fails at listen(), and macOS reports it as EADDRINUSE rather than
ENAMETOOLONG.
Fix: Use a short root. For sandboxes and test runs, something like /tmp/lr-kt, not a nested
scratch directory.
echo -n "$LOCALRUN_DIR/daemon.sock" | wc -c # keep this well under 104Leftover processes after quitting
You see: Ports still held, or a test run failing against empty 200s, after the app is closed.
Cause: The daemon is spawned detached and deliberately outlives the UI. Closing the window does
not stop it, and a leftover Caddy co-binds the port via SO_REUSEPORT.
Fix:
pkill -f 'localrun/resources/caddy' ; pkill -f 'daemon/src/index.ts'For the reasoning behind each of these, and the ones that only matter if you are working on
LocalRun itself, see CLAUDE.md in the repository root.