openclaw - ✅(Solved) Fix Feature: honor OPENCLAW_WRAPPER (or --wrapper) when generating LaunchAgent ProgramArguments [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Fix Action

Fix / Workaround

  • Wrapper: ~/bin/openclaw execs doppler run --project otto --config prd -- node $entry "\$@"
  • Plist needs to be: ProgramArguments = [/Users/me/bin/openclaw, gateway, --port, 18789]
  • After every npm install -g openclaw@<new>, the plist is regenerated as raw node, the wrapper is bypassed, and the gateway's child processes lose their Doppler-injected env. We've patched this manually 8+ times in 4 weeks.

This is a concrete cousin of the workaround already cited in #52771 ("LaunchAgent plist env vars still redacted… wrapper workaround included"). Native support would make that workaround sticky.

PR fix notes

PR #69425: daemon: honor OPENCLAW_WRAPPER in LaunchAgent ProgramArguments (#69400)

Description (problem / solution / changelog)

Fixes #69400.

Summary

Teach resolveCliProgramArguments (the helper that builds macOS LaunchAgent / systemd ProgramArguments for openclaw gateway and openclaw node run services) to honor an OPENCLAW_WRAPPER environment variable. When set to an existing regular file, the resolver emits [<wrapper>, <gateway|node args...>] instead of [<node|bun>, <cli-entrypoint>, <args...>], so secrets-manager shims (Doppler, SOPS, 1Password CLI, sudo/run-as helpers) survive openclaw update / reinstall without manually repatching the generated plist.

Unset or invalid paths (missing file, directory, socket, etc.) transparently fall back to the existing Node/Bun entrypoint resolution, so this is a strict opt-in.

Why

Operators using a launchd service plus a secrets-wrapper binary currently have to re-edit /Library/LaunchAgents/ai.openclaw.gateway.plist after every upgrade because openclaw service install regenerates ProgramArguments from resolveCliProgramArguments and drops the wrapper shim. See the reproduction in #69400.

Implementation

  • src/daemon/program-args.ts: add WRAPPER_ENV_KEY = "OPENCLAW_WRAPPER" plus resolveWrapperProgramArgs(params, env). It trims the env value, resolves the absolute path, fs.stats it, verifies isFile(), and only then returns the shortened programArguments. Any failure (unset/empty env, missing path, non-file) returns null and the existing resolver runs.
  • resolveCliProgramArguments calls the wrapper resolver before any runtime/dev-mode branch. All existing runtime preferences (auto, node, bun, dev) remain unchanged when the wrapper is not active.
  • src/daemon/program-args.test.ts: three regression tests (honor wrapper, ignore when missing, ignore when not a file), gated by process.env.OPENCLAW_WRAPPER within try/finally so they don't leak state. Added fsMocks.stat to the existing vi.hoisted mock pattern.

Operator example

<!-- ~/Library/LaunchAgents/ai.openclaw.gateway.plist -->
<key>EnvironmentVariables</key>
<dict>
  <key>OPENCLAW_WRAPPER</key>
  <string>/opt/homebrew/bin/openclaw-doppler</string>
</dict>

openclaw service install (and any subsequent openclaw update) will now keep emitting ProgramArguments = [/opt/homebrew/bin/openclaw-doppler, gateway, --port, 18789].

Verification

  • pnpm test src/daemon/program-args.test.ts → 9/9 (6 pre-existing + 3 new).
  • Full pnpm tsgo:all hits pre-existing failures in src/security/skill-scanner.test.ts on origin/main, unrelated to this change (verified by stashing and rerunning on bare upstream).

Notes

  • Backwards compatible: no existing plist/systemd unit is rewritten; the feature only kicks in when the env var is explicitly set by the operator.
  • The wrapper is expected to exec the real node/bun + CLI entry with the args it receives. This PR deliberately doesn't prescribe the wrapper contents — that's operator territory.
  • AI-assisted contribution.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/daemon/program-args.test.ts (modified, +81/-0)
  • src/daemon/program-args.ts (modified, +27/-0)

Code Example

# 1) Establish wrapper-form plist by hand
/usr/libexec/PlistBuddy -c 'Set :ProgramArguments:0 ~/bin/openclaw' \
  ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootout gui/\$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootstrap gui/\$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist

# 2) Upgrade
npm install -g openclaw@latest

# 3) Plist is back to raw node — wrapper bypassed
/usr/libexec/PlistBuddy -c 'Print :ProgramArguments:0' \
  ~/Library/LaunchAgents/ai.openclaw.gateway.plist
# /Users/me/.nvm/versions/node/v24.13.0/bin/node     ← the bug
RAW_BUFFERClick to expand / collapse

Problem

When users wrap the openclaw gateway in a shell script (commonly to inject environment from a secrets manager like Doppler/SOPS/1Password), they have to manually re-point ~/Library/LaunchAgents/ai.openclaw.gateway.plist at the wrapper after every install. openclaw service install (and the equivalent path inside gateway install) always emits ProgramArguments = [<resolved node>, <cli entry>, gateway, --port, …], hard-coded in dist/daemon-install-plan.shared-*.js → resolveCliProgramArguments().

Concretely on clawdshell (single-user M2 Mac):

  • Wrapper: ~/bin/openclaw execs doppler run --project otto --config prd -- node $entry "\$@"
  • Plist needs to be: ProgramArguments = [/Users/me/bin/openclaw, gateway, --port, 18789]
  • After every npm install -g openclaw@<new>, the plist is regenerated as raw node, the wrapper is bypassed, and the gateway's child processes lose their Doppler-injected env. We've patched this manually 8+ times in 4 weeks.

This is a concrete cousin of the workaround already cited in #52771 ("LaunchAgent plist env vars still redacted… wrapper workaround included"). Native support would make that workaround sticky.

Proposed shape

  1. Env var: if process.env.OPENCLAW_WRAPPER is set and points at an executable file, resolveCliProgramArguments should emit ProgramArguments = [\$OPENCLAW_WRAPPER, ...params.args] instead of [node, entry, ...params.args].
  2. CLI flag: equivalent --wrapper /path/to/openclaw flag on openclaw service install / openclaw gateway install, persisted into the plist's EnvironmentVariables so subsequent reinstalls remember it.
  3. The wrapper is responsible for exec'ing whatever it wants (Doppler, SOPS, sudo, login shell, etc.) and ultimately calling node + the openclaw entry. openclaw doesn't need to know what's inside.
  4. Doctor signal: if openclaw doctor could surface "plist invokes wrapper at $OPENCLAW_WRAPPER" as an info line, third-party invariant checkers (like otto-doctor) wouldn't have to PlistBuddy-parse the file themselves.

Why this is the right layer

  • The wrapper pattern is the simplest way to get any secrets-manager env into the gateway and its forks (today we have to inline tokens in ~/.openclaw/openclaw.json to dodge the env-redaction issue in #52771; that's a band-aid).
  • Without native support, every user needs an out-of-tree script that re-stamps the plist after each upgrade. We've built one (https://github.com/willtmc/otto-opsbin/otto-gateway-persist) but it shouldn't be necessary.
  • The change is small and backwards-compatible (no env / no flag → existing behavior).

Repro of the regression this would prevent

# 1) Establish wrapper-form plist by hand
/usr/libexec/PlistBuddy -c 'Set :ProgramArguments:0 ~/bin/openclaw' \
  ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootout gui/\$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootstrap gui/\$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist

# 2) Upgrade
npm install -g openclaw@latest

# 3) Plist is back to raw node — wrapper bypassed
/usr/libexec/PlistBuddy -c 'Print :ProgramArguments:0' \
  ~/Library/LaunchAgents/ai.openclaw.gateway.plist
# /Users/me/.nvm/versions/node/v24.13.0/bin/node     ← the bug

Environment

  • openclaw 2026.4.15
  • macOS 15.x arm64, Node 24, npm global install
  • Wrapper: zsh script that execs doppler run --project otto --config prd -- node \$entry "\$@"

Happy to draft the PR if you want — the touch points look small (resolveCliProgramArguments in daemon-install-plan.shared + the install-time CLI parser).

extent analysis

TL;DR

To fix the issue, implement native support for a wrapper script by modifying resolveCliProgramArguments to use the OPENCLAW_WRAPPER environment variable or a new --wrapper flag.

Guidance

  • Set the OPENCLAW_WRAPPER environment variable to point to the wrapper script executable, allowing resolveCliProgramArguments to emit the correct ProgramArguments in the plist.
  • Implement a --wrapper flag on openclaw service install and openclaw gateway install to persist the wrapper path in the plist's EnvironmentVariables.
  • Update the wrapper script to exec the desired command, such as doppler run, and ultimately call the openclaw entry.
  • Consider adding a "doctor signal" to openclaw doctor to surface information about the wrapper invocation.

Example

# Set the OPENCLAW_WRAPPER environment variable
export OPENCLAW_WRAPPER=~/bin/openclaw

# Create a wrapper script (e.g., ~/bin/openclaw) that execs the desired command
echo "exec doppler run --project otto --config prd -- node \$@" > ~/bin/openclaw
chmod +x ~/bin/openclaw

Notes

The proposed solution requires modifying the resolveCliProgramArguments function in daemon-install-plan.shared.js and adding a new --wrapper flag to the install commands. The changes are backwards-compatible and do not affect existing behavior if the OPENCLAW_WRAPPER environment variable is not set or the --wrapper flag is not used.

Recommendation

Apply the proposed solution by implementing the OPENCLAW_WRAPPER environment variable and the --wrapper flag, as it provides a flexible and backwards-compatible way to support wrapper scripts.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Feature: honor OPENCLAW_WRAPPER (or --wrapper) when generating LaunchAgent ProgramArguments [1 pull requests]