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):
export INSTALL_DIR=/Volumes/X/dream-server-test
- Run
dream-server/installers/macos/install-macos.sh --non-interactive
- 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):
mkdir -p "$HOME/Library/Logs/DreamServer"
- 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.
- Keep
WorkingDirectory pointing at INSTALL_DIR — chdir(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#893–Light-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.
Summary
When DreamServer is installed on a volume outside
$HOME(e.g./Volumes/X/dream-server-test), the generated LaunchAgents fail to start withlast exit code = 78 (EX_CONFIG)and produce no log file. Root cause: macOS Sandbox deniesxpcproxy(the launchd spawn proxy)file-write-createpermission to create the plist'sStandardOutPath/StandardErrorPathfiles 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/StandardErrorPathpointing at${INSTALL_DIR}/data/*.log, which is inside the unsandboxed volume.This blocks
dream update,dream restart, and any post-install workflow on installs whoseINSTALL_DIRis not under$HOME.Reproduction
On macOS (Darwin 25.2.0, Apple Silicon):
export INSTALL_DIR=/Volumes/X/dream-server-testdream-server/installers/macos/install-macos.sh --non-interactivelaunchctl list | grep dreamservershows- 78 com.dreamserver.host-agent${INSTALL_DIR}/data/dream-host-agent.logis never createdcurl 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)
```
xpcproxyruns 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 theStandardOutPath/StandardErrorPathbeing 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-agentcomes up immediately on port 7710. The same applies tocom.dreamserver.opencode-web.plist.Proposed fix
In
dream-server/installers/macos/install-macos.sh(around lines 908–977 where the plists are generated):mkdir -p "$HOME/Library/Logs/DreamServer"$HOME/Library/Logs/DreamServer/dream-host-agent.logforStandardOutPath/StandardErrorPathin both the host-agent and opencode-web plists, regardless ofINSTALL_DIR.WorkingDirectorypointing atINSTALL_DIR—chdir(2)to/Volumes/X/...works fine under xpcproxy, onlyopen(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_DIRis outside$HOME. The default\$HOME/dream-serverpath is not affected. Tests that use the default path will not catch this regression — a smoke test that setsINSTALL_DIR=/tmp/ds-smokeor similar would.Environment
/Volumes/X/dream-server-test(auxiliary APFS volume)/Volumes/X/homebrew(non-default prefix)Found during
Integration test run on
local/integration-test=upstream/main+ 17 merged yasinBursali PRs (Light-Heart-Labs#893–Light-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.