#20390: fix(daemon): fall back to /tmp for launchd logs on removable volumes
gateway
size: S
Cluster:
Browser Security Enhancements
## Summary
- macOS launchd refuses to redirect stdout/stderr to paths under `/Volumes/`, causing the LaunchAgent to fail with EPERM when `OPENCLAW_STATE_DIR` (or `~/.openclaw` via symlink) resolves to an external drive
- Detects `/Volumes/` at plist-generation time via `realpathSync` and routes logs to `/tmp/openclaw/` instead
- Respects `OPENCLAW_LOG_PREFIX` in the fallback path
## Motivation
Running OpenClaw from an external SSD (e.g. a portable "seed" drive with all projects) is a valid setup, but `launchctl` silently refuses to write log files to removable volumes. This makes the gateway LaunchAgent fail to start with no obvious error. The fix keeps logs flowing by using `/tmp` as a safe fallback on macOS.
## Test plan
- [x] Added 4 new tests covering: default paths, `/Volumes` fallback, custom prefix with fallback, and non-`/Volumes` passthrough
- [x] All 14 tests pass (`vitest run src/daemon/launchd.test.ts`)
- [x] Lint passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a macOS-specific issue where `launchd` refuses to redirect stdout/stderr to paths on removable volumes (`/Volumes/...`), causing the gateway LaunchAgent to fail silently with EPERM when `OPENCLAW_STATE_DIR` (or `~/.openclaw` via symlink) resolves to an external drive.
- Adds `realpathSync` check in `resolveGatewayLogPaths` to detect when the state directory resolves to `/Volumes/...` on macOS, falling back to `/tmp/openclaw/` for log paths
- Respects `OPENCLAW_LOG_PREFIX` in the fallback path
- Silently catches `realpathSync` errors (e.g., path doesn't exist yet) and defaults to the standard state-dir-based log location
- Adds 4 new tests covering default paths, `/Volumes/` fallback, custom prefix with fallback, and non-`/Volumes/` passthrough (2 tests skip on non-darwin platforms)
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge — it addresses a real macOS limitation with a well-scoped, defensive fix.
- The change is narrowly scoped to a single function (`resolveGatewayLogPaths`), guarded by a platform check (`darwin` only), and handles errors gracefully. The `/Volumes/` detection via `realpathSync` is the correct approach for catching symlinked state directories. All consumers of `resolveGatewayLogPaths` (install, diagnostics, status) will benefit transparently. No existing behavior changes for non-`/Volumes/` setups. Tests cover the key scenarios.
- No files require special attention.
<sub>Last reviewed commit: 7e15a5b</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#23308: fix(browser): accept upload paths that traverse symlinked tmp dirs
by SidQin-cyber · 2026-02-22
80.1%
#18112: fix(daemon): gateway install on macOS ignores fnm/nvm node (#18090)
by yinghaosang · 2026-02-16
78.0%
#22910: fix(browser): resolve symlinks in upload path validation
by erdinccurebal · 2026-02-21
77.8%
#11529: fix(wizard): strip shell-style backslash escapes from workspace paths
by mcaxtr · 2026-02-07
77.6%
#4709: fix(daemon): include user bin dirs in macOS LaunchAgent PATH
by ekson73 · 2026-01-30
77.0%
#12804: fix(daemon): use wrapper script for pnpm global installs in service...
by odinho · 2026-02-09
76.8%
#6273: fix: handle EPIPE errors gracefully in daemon operations
by batumilove · 2026-02-01
76.4%
#18236: macOS daemon: bootstrap LaunchAgent on gateway start after stop
by agisilaos · 2026-02-16
76.1%
#6064: fix(daemon): prefer bundled node from install-cli.sh over system node
by joyshmitz · 2026-02-01
76.0%
#10182: fix: skip non-openclaw LaunchAgents in doctor gateway scan
by Yida-Dev · 2026-02-06
75.8%