openclaw - 💡(How to fix) Fix [Bug]: openclaw gateway install writes markdown hyperlink into plist ProgramArguments, causing gateway crash loop [2 comments, 3 participants]

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…
GitHub stats
openclaw/openclaw#76195Fetched 2026-05-03 04:41:01
View on GitHub
Comments
2
Participants
3
Timeline
4
Reactions
2
Author
Timeline (top)
commented ×2closed ×1unsubscribed ×1

Error Message

#!/usr/bin/env python3 """Fix markdown link corruption in OpenClaw gateway LaunchAgent plist."""

import re, shutil from datetime import datetime from pathlib import Path

PLIST = Path.home() / "Library/LaunchAgents/ai.openclaw.gateway.plist" BROKEN = re.compile(r"[(.+?ai.openclaw.gateway-env-wrapper.sh)]([^)]+)")

def main(): if not PLIST.exists(): print("ERROR: plist not found"); return content = open(PLIST).read() if BROKEN.search(content): backup = PLIST.with_suffix(f".plist.bak-{datetime.now():%Y%m%d-%H%M%S}") shutil.copy2(PLIST, backup) print(f"Backup: {backup}") fixed = BROKEN.sub(r"\1", content) open(PLIST, "w").write(fixed) print("Fixed — markdown link removed.") else: print("Clean — no corruption found.") if BROKEN.search(open(PLIST).read()): print("FAILED — check manually.") else: print("Verified clean.")

if name == "main": main()

Root Cause

Root Cause (Likely)

Fix Action

Workaround

Run a post-install script that strips the markdown link syntax and restores the plain path:

#!/usr/bin/env python3
"""Fix markdown link corruption in OpenClaw gateway LaunchAgent plist."""

import re, shutil
from datetime import datetime
from pathlib import Path

PLIST = Path.home() / "Library/LaunchAgents/ai.openclaw.gateway.plist"
BROKEN = re.compile(r"\[(.+?ai\.openclaw\.gateway-env-wrapper\.sh)\]\([^)]+\)")

def main():
    if not PLIST.exists():
        print("ERROR: plist not found"); return
    content = open(PLIST).read()
    if BROKEN.search(content):
        backup = PLIST.with_suffix(f".plist.bak-{datetime.now():%Y%m%d-%H%M%S}")
        shutil.copy2(PLIST, backup)
        print(f"Backup: {backup}")
        fixed = BROKEN.sub(r"\1", content)
        open(PLIST, "w").write(fixed)
        print("Fixed — markdown link removed.")
    else:
        print("Clean — no corruption found.")
    if BROKEN.search(open(PLIST).read()):
        print("FAILED — check manually.")
    else:
        print("Verified clean.")

if __name__ == "__main__":
    main()

Then reload:

launchctl bootout gui/502/ai.openclaw.gateway
launchctl bootstrap gui/502 ~/Library/LaunchAgents/ai.openclaw.gateway.plist

Code Example

<string>[/Users/me/.openclaw/service-env/ai.openclaw.gateway-env-wrapper.sh](https://...)</string>

---

#!/usr/bin/env python3
"""Fix markdown link corruption in OpenClaw gateway LaunchAgent plist."""

import re, shutil
from datetime import datetime
from pathlib import Path

PLIST = Path.home() / "Library/LaunchAgents/ai.openclaw.gateway.plist"
BROKEN = re.compile(r"\[(.+?ai\.openclaw\.gateway-env-wrapper\.sh)\]\([^)]+\)")

def main():
    if not PLIST.exists():
        print("ERROR: plist not found"); return
    content = open(PLIST).read()
    if BROKEN.search(content):
        backup = PLIST.with_suffix(f".plist.bak-{datetime.now():%Y%m%d-%H%M%S}")
        shutil.copy2(PLIST, backup)
        print(f"Backup: {backup}")
        fixed = BROKEN.sub(r"\1", content)
        open(PLIST, "w").write(fixed)
        print("Fixed — markdown link removed.")
    else:
        print("Clean — no corruption found.")
    if BROKEN.search(open(PLIST).read()):
        print("FAILED — check manually.")
    else:
        print("Verified clean.")

if __name__ == "__main__":
    main()

---

launchctl bootout gui/502/ai.openclaw.gateway
launchctl bootstrap gui/502 ~/Library/LaunchAgents/ai.openclaw.gateway.plist
RAW_BUFFERClick to expand / collapse

Bug

When openclaw gateway install (or gateway install --force) generates the macOS LaunchAgent plist, the env-wrapper shell path in ProgramArguments[0] is written as a markdown hyperlink instead of a plain filesystem path:

<string>[/Users/me/.openclaw/service-env/ai.openclaw.gateway-env-wrapper.sh](https://...)</string>

This produces a literally unexecutable filename containing [, ], (, ), and a URL. launchd cannot resolve the path, and the gateway dies instantly on every start attempt — resulting in a crash loop.

Impact

  • Gateway crash loop after every openclaw update / gateway install --force
  • Requires manual plist repair to recover
  • Affects macOS (arm64), OpenClaw v2026.4.29

Root Cause (Likely)

The env-wrapper path appears to be piped through a markdown formatter before being serialized into the plist XML. Same class of bug as #13340 (redacted strings leaking into plist) — formatted/processed strings reaching the raw plist instead of plain values.

Reproduction

  1. openclaw gateway install --force
  2. Inspect plist: plutil -p ~/Library/LaunchAgents/ai.openclaw.gateway.plist
  3. ProgramArguments[0] contains markdown link syntax around the env-wrapper path
  4. Gateway fails to start (crash loop)

Workaround

Run a post-install script that strips the markdown link syntax and restores the plain path:

#!/usr/bin/env python3
"""Fix markdown link corruption in OpenClaw gateway LaunchAgent plist."""

import re, shutil
from datetime import datetime
from pathlib import Path

PLIST = Path.home() / "Library/LaunchAgents/ai.openclaw.gateway.plist"
BROKEN = re.compile(r"\[(.+?ai\.openclaw\.gateway-env-wrapper\.sh)\]\([^)]+\)")

def main():
    if not PLIST.exists():
        print("ERROR: plist not found"); return
    content = open(PLIST).read()
    if BROKEN.search(content):
        backup = PLIST.with_suffix(f".plist.bak-{datetime.now():%Y%m%d-%H%M%S}")
        shutil.copy2(PLIST, backup)
        print(f"Backup: {backup}")
        fixed = BROKEN.sub(r"\1", content)
        open(PLIST, "w").write(fixed)
        print("Fixed — markdown link removed.")
    else:
        print("Clean — no corruption found.")
    if BROKEN.search(open(PLIST).read()):
        print("FAILED — check manually.")
    else:
        print("Verified clean.")

if __name__ == "__main__":
    main()

Then reload:

launchctl bootout gui/502/ai.openclaw.gateway
launchctl bootstrap gui/502 ~/Library/LaunchAgents/ai.openclaw.gateway.plist

Suggested Fix

Ensure the plist serialization path uses raw/unformatted strings, not markdown-formatted output. The installLaunchAgent / buildGatewayInstallPlan functions should write plain filesystem paths to ProgramArguments, bypassing any markdown/rich-text formatting pipeline.

Related

  • #13340 — same class (formatted strings leaking into plist)
  • #40811 — gateway entrypoint mismatch after update
  • #26507 — KeepAlive crash loop at midnight

Environment

  • OpenClaw v2026.4.29
  • macOS 26 (15.4) arm64
  • Node v22.22.0
  • pnpm global install

extent analysis

TL;DR

Run a post-install script to strip markdown link syntax from the OpenClaw gateway LaunchAgent plist to fix the crash loop.

Guidance

  • Verify the issue by inspecting the plist file using plutil -p ~/Library/LaunchAgents/ai.openclaw.gateway.plist and checking for markdown link syntax in ProgramArguments[0].
  • Use the provided Python script to fix the corruption by stripping the markdown link syntax and restoring the plain path.
  • After running the script, reload the LaunchAgent using launchctl bootout and launchctl bootstrap to apply the changes.
  • To prevent future occurrences, ensure that the plist serialization path uses raw/unformatted strings, bypassing any markdown/rich-text formatting pipeline.

Example

The provided Python script can be used as an example of how to fix the corruption:

BROKEN = re.compile(r"\[(.+?ai\.openclaw\.gateway-env-wrapper\.sh)\]\([^)]+\)")
fixed = BROKEN.sub(r"\1", content)

This code uses a regular expression to match the markdown link syntax and replaces it with the plain path.

Notes

This fix is specific to OpenClaw v2026.4.29 and macOS 26 (15.4) arm64. The root cause is likely due to formatted strings leaking into the plist, similar to issue #13340.

Recommendation

Apply the workaround by running the post-install script to fix the corruption, as a permanent fix would require changes to the installLaunchAgent / buildGatewayInstallPlan functions to use raw/unformatted strings.

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