Skip to content

macOS launchd: StandardOutPath on non-$HOME volume is blocked by xpcproxy sandbox → host-agent exits 78 #328

@yasinBursali

Description

@yasinBursali

Summary

When DreamServer is installed on a volume outside $HOME (e.g. /Volumes/X/dream-server-test), the generated LaunchAgents fail to start with last exit code = 78 (EX_CONFIG) and produce no log file. Root cause: macOS Sandbox denies xpcproxy (the launchd spawn proxy) file-write-create permission to create the plist's StandardOutPath / StandardErrorPath files on non-standard volumes, so the spawn aborts before the target process ever runs.

PR Light-Heart-Labs#899 (dynamic launchd PATH + extensions-lib source) fixed the PATH issue but does not cover this case: it still generates plists with StandardOutPath / StandardErrorPath pointing at ${INSTALL_DIR}/data/*.log, which is inside the unsandboxed volume.

This blocks dream update, dream restart, and any post-install workflow on installs whose INSTALL_DIR is not under $HOME.

Reproduction

On macOS (Darwin 25.2.0, Apple Silicon):

  1. export INSTALL_DIR=/Volumes/X/dream-server-test
  2. Run dream-server/installers/macos/install-macos.sh --non-interactive
  3. Install reaches Phase 6/6 verification and reports "LaunchAgent installed (port 7710)" — but:
    • launchctl list | grep dreamserver shows - 78 com.dreamserver.host-agent
    • ${INSTALL_DIR}/data/dream-host-agent.log is never created
    • curl http://127.0.0.1:7710/ fails (nothing listening)

Evidence

launchctl print gui/\$UID/com.dreamserver.host-agent

```
state = spawn scheduled
program = /Volumes/X/homebrew/Cellar/python@3.14/3.14.3_1/Frameworks/Python.framework/Versions/3.14/bin/python3.14
working directory = /Volumes/X/dream-server-test
stdout path = /Volumes/X/dream-server-test/data/dream-host-agent.log
stderr path = /Volumes/X/dream-server-test/data/dream-host-agent.log
runs = 2
last exit code = 78: EX_CONFIG
```

Smoking gun — /usr/bin/log show --predicate 'eventMessage CONTAINS "dream-host-agent"'

```
E kernel[0] (Sandbox) System Policy: xpcproxy(3693) deny(1) file-write-create /Volumes/X/dream-server-test/data/dream-host-agent.log
E kernel[0] (Sandbox) System Policy: xpcproxy(3720) deny(1) file-write-create /Volumes/X/dream-server-test/data/dream-host-agent.log
E kernel[0] (Sandbox) System Policy: xpcproxy(3753) deny(1) file-write-create /Volumes/X/dream-server-test/data/dream-host-agent.log
... (repeating every 10s as launchd retries)
```

xpcproxy runs under a restrictive sandbox profile that disallows file creation outside the user's home + a few standard paths. It cannot create files on /Volumes/X (or any non-standard volume).

Proof that the plist itself is correct

Identical plist config with a different label and StandardOutPath=/tmp/... works fine:
```
$ cat /tmp/test-launchd2.plist # same ProgramArguments, same WorkingDirectory, same env
$ launchctl bootstrap gui/$UID /tmp/test-launchd2.plist
$ launchctl list | grep diag-direct
4322 0 com.dreamserver.diag-direct
$ cat /tmp/ds-diag-direct.log
2026-04-11 23:47:51 [INFO] dream-host-agent: Dream Host Agent v1.0.0 listening on 127.0.0.1:7710
2026-04-11 23:47:51 [INFO] dream-host-agent: Install dir: /Volumes/X/dream-server-test | GPU: apple | Tier: 1
```

So it isn't PATH, isn't the Python interpreter path, isn't WorkingDirectory, and isn't the label history — it's purely the StandardOutPath / StandardErrorPath being on a sandboxed volume.

Workaround (verified locally)

Redirect the LaunchAgent log paths to $HOME/Library/Logs/DreamServer/, which is always writable to xpcproxy:

```xml
StandardOutPath
/Users/yasinbursali/Library/Logs/DreamServer/dream-host-agent.log
StandardErrorPath
/Users/yasinbursali/Library/Logs/DreamServer/dream-host-agent.log
```

After this change (+ bootout/bootstrap cycle), host-agent comes up immediately on port 7710. The same applies to com.dreamserver.opencode-web.plist.

Proposed fix

In dream-server/installers/macos/install-macos.sh (around lines 908–977 where the plists are generated):

  1. mkdir -p "$HOME/Library/Logs/DreamServer"
  2. Use $HOME/Library/Logs/DreamServer/dream-host-agent.log for StandardOutPath/StandardErrorPath in both the host-agent and opencode-web plists, regardless of INSTALL_DIR.
  3. Keep WorkingDirectory pointing at INSTALL_DIRchdir(2) to /Volumes/X/... works fine under xpcproxy, only open(O_CREAT) for the log paths is denied.

This is symmetric with what Apple uses for user agents (TextEdit, etc.), survives installs to any volume, and is discoverable via ~/Library/Logs/DreamServer/.

Note: this bug only affects installs where INSTALL_DIR is outside $HOME. The default \$HOME/dream-server path is not affected. Tests that use the default path will not catch this regression — a smoke test that sets INSTALL_DIR=/tmp/ds-smoke or similar would.

Environment

  • macOS: Darwin 25.2.0
  • Python: 3.14.3 (Homebrew)
  • Docker: 28.1.1 (Docker Desktop for Mac)
  • Install location: /Volumes/X/dream-server-test (auxiliary APFS volume)
  • Homebrew: /Volumes/X/homebrew (non-default prefix)

Found during

Integration test run on local/integration-test = upstream/main + 17 merged yasinBursali PRs (Light-Heart-Labs#893Light-Heart-Labs#909). All 17 PRs merged cleanly and static validation passed; this bug surfaced at the Phase 4 "fresh Mac install" step when I tried to run the installed stack and launchd services refused to start.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions