fix: allow macOS gateway restart while LaunchAgent owns its port#89096
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 20, 2026, 3:45 PM ET / 19:45 UTC. Summary PR surface: Source +20, Tests +84. Total +104 across 2 files. Reproducibility: yes. The PR supplies a concrete macOS LaunchAgent Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the exact-PID ownership guard after a current-main refresh, retaining fail-closed diagnostics for every listener set that cannot be proven to belong solely to the current LaunchAgent service. Do we have a high-confidence way to reproduce the issue? Yes. The PR supplies a concrete macOS LaunchAgent Is this the best way to solve the issue? Yes. Comparing listener PIDs to the freshly read AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against e2bb04328f5c. Label changesLabel justifications:
Evidence reviewedPR surface: Source +20, Tests +84. Total +104 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (7 earlier review cycles)
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
This pull request has been automatically marked as stale due to inactivity. |
ec7dbdc to
ce59324
Compare
ce59324 to
17ffdaa
Compare
|
Exact-head macOS runtime proof is complete for
Managed KeepAlive ownerA disposable The exact-head Unrelated port ownerA second disposable The exact-head restart rejected it with: The fail-closed assertions all passed:
Both disposable LaunchAgents, listeners, and temporary homes were cleaned up by the harness. The run concluded successfully with |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Protect the managed LaunchAgent PID, re-read after cleanup, and reject mixed or unattributed port ownership before service mutation. Co-authored-by: Shawn Duggan <[email protected]>
|
Maintainer landing proof for final exact head The final four-file patch fixes both ownership races:
Proof completed:
Known proof gap: mixed and missing-PID listener sets are deterministic unit cases because healthy macOS Contributor credit for @shawnduggan is preserved. Broader SIGTERM and in-process lifecycle work remains tracked separately in #88309. |
|
Resolved in exact head Stale cleanup now refreshes the protected launchd PID after listener enumeration and before candidate filtering or signal delivery. A KeepAlive replacement captured by The final Codex autoreview is clean, exact-head CI has no pending or failed checks, and the hosted prepare gate passed. The current head is land-ready. |
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: fix: allow macOS gateway restart while LaunchAgent owns its port This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
|
Merged via squash.
|
…nclaw#89096) * fix(daemon): allow launchd restart while gateway port is busy * fix(daemon): verify launchd restart port ownership * fix(macos): refresh launchd restart ownership guard Protect the managed LaunchAgent PID, re-read after cleanup, and reject mixed or unattributed port ownership before service mutation. Co-authored-by: Shawn Duggan <[email protected]> * fix(macos): refresh launchd pid during stale cleanup --------- Co-authored-by: Peter Steinberger <[email protected]>
Related: #88309
What Problem This Solves
Fixes an issue where users running the gateway as a macOS LaunchAgent could have
openclaw gateway restartfail with a port-busy error when the current, healthy LaunchAgent gateway was the process listening on that port.This is a normal restart state, not necessarily a conflict: the service being replaced usually owns the configured port, and launchd's
KeepAlivepolicy can also respawn it during stale-process cleanup. The previous code treated every listener that remained after cleanup as a fatal conflict and threw before plist repair,launchctl enable, orlaunchctl kickstart -kcould run.The regression comes from the interaction of two otherwise-defensive changes:
KeepAliveLaunchAgent, cleanup could terminate the managed PID, launchd could immediately replace it, and the generic check would then reject the replacement for owning its own port.The broader #88309 also discusses SIGTERM intent and in-process restart handoff behavior. This PR deliberately fixes only the external/direct LaunchAgent restart branch, so it uses
Relatedrather thanCloses.Why This Change Was Made
The restart preflight now distinguishes the managed listener from an unrelated port owner by authoritative process identity:
--portand environment fallback rules.launchctl printbefore cleanup and pass it through the existingprotectedPidcleanup option. A healthy supervised process is not stale and should not be killed merely to create a momentary free-port observation.The resulting decision is intentionally narrow:
enable,bootout,bootstrap, orkickstartThis is the right ownership test and boundary for several reasons:
KeepAlivereplaces the process during the cleanup window, the decision uses the current launchd PID rather than a stale pre-cleanup value.protectedPidseam already supported by stale cleanup and avoids an unnecessary first termination followed by the requested launchd restart.exec "$@", and the documented custom-wrapper contract requires the wrapper to eventuallyexecOpenClaw or Node. The PID supervised by launchd is therefore expected to be the PID holding the gateway listener.service.restartwithout the CLI readiness wrapper. Rejecting an unrelated owner insiderestartLaunchAgentprotects those callers as well as the CLI.kickstart -kpath; plist changes still use the existingbootout/bootstrapreload path; launchctl failures still propagate.Alternatives considered:
KeepAlive; launchd is allowed to replace the process immediately.The change is limited to the macOS LaunchAgent implementation and focused tests. The detached in-process handoff branch returns before this preflight and is unchanged. Stop behavior, systemd, Windows Scheduled Tasks, configuration, authentication, persistence, dependencies, and public APIs are unchanged.
User Impact
openclaw gateway restartcan now restart a macOS LaunchAgent when that LaunchAgent's own gateway is listening on the configured port. Users no longer need to manually stop the service or race launchd'sKeepAlivebehavior.If another process owns any listener on that port, restart still stops safely before touching the plist or service and reports the listener diagnostics. There is no migration, configuration change, new permission, or dependency change.
Evidence
Exact-head implementation and regression coverage
Current head:
17ffdaac8cce, rebased ontoe0206b4967a6.4242, verifies cleanup receives{ protectedPid: 4242 }, verifies the post-cleanup runtime read, and reachesenablepluskickstart -k.5151, verifies the diagnostic error, and proves that the plist is unchanged, no file write occurs, and noenable,bootout, orkickstartmutation is issued.--portflags, invalid stored ports, reload/bootstrap behavior, and the no-explicit-port path.git diff --check origin/main...HEADpasses.Exact-head GitHub Actions are complete: 79 checks passed, 36 path/matrix checks were intentionally skipped, and 0 failed. The primary CI run 29761958034 concluded successfully, including:
openclaw/ci-gatecheck-test-typesandcheck-prod-typescheck-lintbuild-artifactsThe exact-head Real behavior proof, security, CodeQL, dependency, workflow-sanity, and OpenGrep checks are also green.
Real macOS reproduction and supervisor behavior
The original June 1 macOS proof used a disposable profile (
pr89096proof), disposable LaunchAgent (ai.openclaw.pr89096proof), and port19896. It is reproduction/supervisor evidence from proof head601a25481223, not a claim that the final ownership guard was live-run on the current head.Before restart, launchd reported PID
68831, andlsofshowed that same PID owning both loopback listeners:The earlier patch reached launchd, which replaced PID
68831with PID69148; deep status and health then passed:That run demonstrates the real
KeepAlive/busy-port condition and thatkickstart -kperforms the intended replacement. The final patch makes the preflight safer than that proof head: it protects PID68831from stale cleanup and permits the restart only because the listener PIDs match launchd's authoritative PID.AI-assisted: Codex was used to trace callers and platform contracts, inspect the relevant history, implement and review the ownership invariant, and prepare this explanation. Claims above are tied to exact-head source/tests, CI, and the labeled macOS behavior transcript.