Skip to content

Updater submits manual-update job via launchctl submit → KeepAlive defaults true → infinite update loop that repeatedly stops the gateway #88736

Description

@deonkretch

Summary

On macOS, openclaw update schedules its out-of-process update job using launchctl submit. launchctl submit creates the job with KeepAlive = true by default, so the update runner (run-openclaw-update.zsh) is relaunched immediately every time it exits. Each iteration runs openclaw update, whose first action is "Stopping managed gateway service before package update" — so the managed gateway is killed roughly once per minute and never stays up. The package update itself succeeds on the first pass; the loop continues forever afterward with availability.available = false.

Net user-visible symptom: after updating, the gateway won't stay up.

Environment

  • OpenClaw 2026.5.28 (commit e932160); first observed after auto-update 2026.5.26 → 2026.5.28
  • Install: global package at /opt/homebrew/lib/node_modules/openclaw (pnpm)
  • node v25.6.1
  • macOS 26.4 (arm64)

Impact

  • Managed gateway (ai.openclaw.gateway) is force-stopped ~1×/minute indefinitely → all agents/channels (Discord, Telegram, cron) keep dropping.
  • Two jobs were submitted ~1s apart (ai.openclaw.manual-update.<epoch>), so the gateway was being stopped by two concurrent loops.
  • ~/.openclaw/work/update-logs/openclaw-update-*.log grew to ~2 MB / 80+ repeated == START / == UPDATE / == DOCTOR cycles in ~80 min.
  • ~/.openclaw/logs/gateway-restart.log fills with Could not find service "ai.openclaw.gateway" in domain for user gui: 501 as the loop races with itself (kickstart attempted against a service that a concurrent iteration has just booted out).

Root cause

The submitted launchd jobs are KeepAlive. Confirmed via:

$ launchctl print gui/501/ai.openclaw.manual-update.<epoch>
    path = (submitted by launchctl[...])
    program = /Users/<user>/.openclaw/work/update-logs/run-openclaw-update.zsh
    properties = keepalive | inferred program

path = (submitted by launchctl ...) + properties = keepalive = a launchctl submit job using the default unconditional KeepAlive. run-openclaw-update.zsh exits cleanly (DOCTOR_EXIT 0) each cycle, and launchd relaunches it in the same second:

== END   Sun May 31 19:49:38 SAST 2026 ==
== START Sun May 31 19:49:38 SAST 2026 ==

Steps to reproduce

  1. On macOS, trigger an update that uses the launchctl-submit path (e.g. auto-update or openclaw update).
  2. Observe a transient ai.openclaw.manual-update.<epoch> job appear: launchctl list | grep manual-update.
  3. launchctl print gui/$(id -u)/ai.openclaw.manual-update.<epoch>properties = keepalive.
  4. Watch the gateway get stopped each cycle (gateway-restart.log / update log) and never stay up.

Expected

The update job runs once and exits. The gateway is stopped only for the actual package swap, then restarted and left running.

Suggested fix

launchctl submit is deprecated and defaults to unconditional KeepAlive — it's the wrong primitive for a run-once task. Options:

  1. Preferred: write a temporary LaunchAgent plist with KeepAlive = false (or omit it) and RunAtLoad = true, bootstrap it, and bootout it on completion — instead of launchctl submit.
  2. If staying with launchctl submit, the runner must remove its own job on exit (e.g. launchctl remove ai.openclaw.manual-update.<epoch> as the last step) so it can't be relaunched.
  3. Defensive: never submit a second update job while one is already registered (the two near-simultaneous submits doubled the damage).
  4. The "stop the gateway before update" step should be gated on an update actually being applied — when availability.available == false, the job should no-op instead of stopping the gateway.

Workaround (for affected users)

# tear down the runaway loop
for l in $(launchctl list | awk '/ai\.openclaw\.manual-update/ {print $3}'); do launchctl bootout gui/$(id -u)/$l; done
pkill -f run-openclaw-update.zsh; pkill -f openclaw-doctor
# the gateway is left booted out — reload it
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl kickstart -k gui/$(id -u)/ai.openclaw.gateway

The submitted jobs are not persisted to disk, so they don't survive logout/reboot — but the next update re-triggers the loop until this is fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions