Skip to content

fix(gateway): persist macOS stop disable after bootout#79235

Merged
steipete merged 5 commits into
mainfrom
fix/gateway-stop-disable-after-bootout
May 8, 2026
Merged

fix(gateway): persist macOS stop disable after bootout#79235
steipete merged 5 commits into
mainfrom
fix/gateway-stop-disable-after-bootout

Conversation

@steipete

@steipete steipete commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Bug / Behavior

#78412 changes the default macOS stop path from launchctl disable to launchctl bootout, preserving KeepAlive recovery unless the operator explicitly requests --disable. On the PR head, that explicit stay-down path still failed after a previous bootout: launchctl print returned not-loaded, runServiceStop returned not-loaded, and stopLaunchAgent(..., disable: true) never ran, leaving launchctl print-disabled gui/501 as "ai.openclaw.gateway" => enabled.

This PR keeps the default bootout behavior, but lets macOS stop --disable continue through the service manager even when launchd says the service is not loaded, so the disable bit is actually persisted.

Fixes #77934.
Fixes #77428.
Replaces #78412.

Real behavior proof

  • Behavior addressed: macOS openclaw gateway stop --disable must persist the LaunchAgent disable bit even after a prior default stop used launchctl bootout and left the service not loaded.
  • Real environment tested: Parallels macOS Tahoe VM, Darwin 25.3.0, uid 501, isolated OPENCLAW_STATE_DIR=$HOME/.openclaw-78412-proof and OPENCLAW_CONFIG_PATH=$HOME/.openclaw-78412-proof/openclaw.json.
  • Exact steps or command run after this patch: Installed the patched package, ran openclaw gateway install --force --token proof-token --port 18789 --json, then openclaw gateway stop --json, then openclaw gateway stop --disable --json, then inspected launchctl print-disabled gui/501 and launchctl print gui/501/ai.openclaw.gateway.
  • Evidence after fix: Terminal output from the patched package showed default stop preserving the enabled map and explicit disable flipping it:
install: ok=true result=installed
launchctl print-disabled gui/501: "ai.openclaw.gateway" => enabled

openclaw gateway stop --json:
{ "action": "stop", "ok": true, "result": "stopped", "service": { "label": "LaunchAgent", "loaded": false } }
launchctl print-disabled gui/501: "ai.openclaw.gateway" => enabled
launchctl print gui/501/ai.openclaw.gateway: rc=113 Could not find service

openclaw gateway stop --disable --json:
{ "action": "stop", "ok": true, "result": "stopped", "service": { "label": "LaunchAgent", "loaded": false } }
launchctl print-disabled gui/501: "ai.openclaw.gateway" => disabled
  • Observed result after fix: The default bootout path still leaves KeepAlive recovery enabled, while the explicit --disable path now persists "ai.openclaw.gateway" => disabled after the service is already not loaded.
  • What was not tested: Nothing else.

For comparison, the unpatched #78412 head 2be5de518334ae350422a14fdccfa526603c4376 returned result: "not-loaded" for openclaw gateway stop --disable --json after default stop, and launchctl print-disabled gui/501 stayed "ai.openclaw.gateway" => enabled.

Verification

  • pnpm install
  • pnpm test src/cli/daemon-cli/lifecycle-core.test.ts src/cli/daemon-cli/lifecycle.test.ts src/daemon/launchd.test.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/cli/daemon-cli/lifecycle-core.ts src/cli/daemon-cli/lifecycle.ts src/cli/daemon-cli/lifecycle-core.test.ts src/cli/daemon-cli/lifecycle.test.ts docs/cli/gateway.md docs/gateway/index.md src/daemon/launchd.ts src/daemon/launchd.test.ts src/cli/daemon-cli/register-service-commands.ts src/cli/daemon-cli/types.ts src/daemon/service-types.ts
  • git diff --check origin/main...HEAD
  • pnpm build
  • Parallels macOS Tahoe VM reproduce/fix proof above

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime cli CLI command changes size: M maintainer Maintainer-authored PR labels May 8, 2026
@steipete
steipete merged commit 1f88cb2 into main May 8, 2026
108 of 118 checks passed
@steipete
steipete deleted the fix/gateway-stop-disable-after-bootout branch May 8, 2026 04:35

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4c239c461

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +387 to +390
if (params.stopWhenNotLoaded) {
try {
await params.service.stop({ env: process.env, stdout, disable: params.opts?.disable });
} catch (err) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve unmanaged stop fallback for macOS --disable

When openclaw gateway stop --disable runs on macOS and launchd reports the service as not loaded, this new branch calls service.stop(...) and returns stopped without invoking the existing onNotLoaded unmanaged-process path. In that scenario, a gateway started outside launchd (but still listening on the port) is left running even though the CLI reports success, because only the launchd disable/stop commands are attempted. This regresses stop semantics specifically for not-loaded + unmanaged runtime cases.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

2 participants