#22671: Preserve custom env vars in LaunchAgent plist across updates
gateway
size: S
trusted-contributor
Cluster:
Node and macOS Enhancements
## Problem
`openclaw update` regenerates the LaunchAgent plist from scratch, wiping user-added `EnvironmentVariables`. This breaks setups that rely on custom env vars like `NODE_OPTIONS` for IPv6 workarounds on Node.js 22+.
Users have to manually re-patch the plist after every update.
## Fix
Before building the new plist, read the existing one via `readLaunchAgentProgramArgumentsFromFile()` (already available in `launchd-plist.ts`) and merge its environment variables as defaults. Explicitly provided values from the new install take precedence, so managed keys like `OPENCLAW_GATEWAY_TOKEN` are still updated correctly.
```ts
const existing = await readLaunchAgentProgramArgumentsFromFile(plistPath);
const mergedEnvironment = { ...existing?.environment, ...environment };
```
## Test
Added test that installs a LaunchAgent with `NODE_OPTIONS`, then reinstalls without it, and verifies `NODE_OPTIONS` is preserved while `OPENCLAW_GATEWAY_TOKEN` is correctly updated to the new value.
Closes #22663
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Merges existing environment variables from the LaunchAgent plist before reinstall to preserve user-added custom env vars like `NODE_OPTIONS` across updates. Explicitly provided keys still override preserved values, ensuring managed variables like `OPENCLAW_GATEWAY_TOKEN` update correctly.
- Reads existing plist via `readLaunchAgentProgramArgumentsFromFile()` (which safely returns null if file doesn't exist)
- Merges environment with spread operator: `{...existing?.environment, ...environment}`
- Test verifies `NODE_OPTIONS` persists while `OPENCLAW_GATEWAY_TOKEN` updates to new value
- Test structure issue: new test is outside the `describe("launchd install")` block
<h3>Confidence Score: 4/5</h3>
- Safe to merge after fixing test structure issue
- Implementation is clean and correct with proper null-safety via optional chaining, but test placement outside describe block breaks test organization conventions
- Fix test structure in `src/daemon/launchd.test.ts` (test should be inside describe block)
<sub>Last reviewed commit: 817b557</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22929: Fix NODE_EXTRA_CA_CERTS missing from LaunchAgent environment on macOS
by Clawborn · 2026-02-21
76.9%
#21164: feat(config): add lenient mode to resolveConfigEnvVars — preserve g...
by Mellowambience · 2026-02-19
75.3%
#9200: Fix: Strip dangerous env vars from baseEnv in host execution
by vishaltandale00 · 2026-02-05
74.7%
#11327: fix(launchd): reload plist from disk on restartLaunchAgent
by caiop91 · 2026-02-07
74.4%
#15619: fix: clean up orphan LaunchAgent plist on bootstrap failure
by superlowburn · 2026-02-13
74.0%
#20390: fix(daemon): fall back to /tmp for launchd logs on removable volumes
by lemoz · 2026-02-18
73.7%
#16658: test: isolate env-dependent gateway/auth fixtures
by sauerdaniel · 2026-02-15
73.5%
#19885: test(gateway,browser): isolate tests from ambient OPENCLAW_GATEWAY_...
by NewdlDewdl · 2026-02-18
73.4%
#23055: test: clear gateway env vars in beforeEach to prevent leakage
by thinstripe · 2026-02-21
73.3%
#23139: test: fix flaky auth tests when OPENCLAW_GATEWAY_TOKEN is present
by Imccccc · 2026-02-22
72.9%