Skip to content

fix: allow macOS gateway restart while LaunchAgent owns its port#89096

Merged
steipete merged 4 commits into
openclaw:mainfrom
shawnduggan:fix/launchd-restart-busy-port
Jul 21, 2026
Merged

fix: allow macOS gateway restart while LaunchAgent owns its port#89096
steipete merged 4 commits into
openclaw:mainfrom
shawnduggan:fix/launchd-restart-busy-port

Conversation

@shawnduggan

@shawnduggan shawnduggan commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Related: #88309

What Problem This Solves

Fixes an issue where users running the gateway as a macOS LaunchAgent could have openclaw gateway restart fail 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 KeepAlive policy 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, or launchctl kickstart -k could run.

The regression comes from the interaction of two otherwise-defensive changes:

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 Related rather than Closes.

Why This Change Was Made

The restart preflight now distinguishes the managed listener from an unrelated port owner by authoritative process identity:

  1. Resolve the effective gateway port from the installed LaunchAgent configuration, preserving the existing final---port and environment fallback rules.
  2. Read the current service PID from launchctl print before cleanup and pass it through the existing protectedPid cleanup option. A healthy supervised process is not stale and should not be killed merely to create a momentary free-port observation.
  3. Run the existing targeted stale-gateway cleanup for any other verified gateway process.
  4. Inspect the port. If it is busy, re-read the LaunchAgent runtime after cleanup to account for a concurrent launchd respawn.
  5. Continue only when a current managed PID exists, at least one listener was observed, and every listener row has that exact PID. Otherwise, fail with the existing detailed port diagnostics before rewriting the plist or mutating launchd state.

The resulting decision is intentionally narrow:

Port state after cleanup Restart behavior
Free Continue through the existing launchd restart path
Busy, and every listener PID equals the current LaunchAgent PID Continue through the existing launchd restart path
Busy with a missing PID, mixed PIDs, no current LaunchAgent PID, or an unrelated PID Reject before plist writes, enable, bootout, bootstrap, or kickstart

This is the right ownership test and boundary for several reasons:

  • launchd is the authority for service identity. Command names and command lines are diagnostic metadata; they can be incomplete, wrapper-dependent, or misleading. Comparing listener PIDs to the PID reported for the exact LaunchAgent service avoids relying on a spoofable “looks like OpenClaw” heuristic.
  • All listener rows must match. A normal dual-stack gateway produces separate IPv4 and IPv6 rows for one PID, which is accepted. Mixed ownership, missing attribution, and partial attribution fail closed.
  • The PID is re-read after cleanup. If KeepAlive replaces the process during the cleanup window, the decision uses the current launchd PID rather than a stale pre-cleanup value.
  • The pre-cleanup PID is protected. This reuses the protectedPid seam already supported by stale cleanup and avoids an unnecessary first termination followed by the requested launchd restart.
  • The PID relationship is supported by the service contract. OpenClaw's generated environment wrapper ends with exec "$@", and the documented custom-wrapper contract requires the wrapper to eventually exec OpenClaw or Node. The PID supervised by launchd is therefore expected to be the PID holding the gateway listener.
  • The guard lives at the service-owner boundary. Some internal callers invoke service.restart without the CLI readiness wrapper. Rejecting an unrelated owner inside restartLaunchAgent protects those callers as well as the CLI.
  • Existing launchd behavior remains in charge after the guard. Normal restarts still use the lower-downtime kickstart -k path; plist changes still use the existing bootout/bootstrap reload path; launchctl failures still propagate.
  • Stop and restart retain different postconditions. Stop still requires the port to become free. Restart permits continuous ownership only when that ownership is proven to belong to the service being replaced.

Alternatives considered:

  • Removing the busy-port guard entirely would fix the managed-listener case but could disrupt a working gateway and then fail to bind its replacement when an unrelated process owns the port. It is also unsafe for direct callers without the CLI health wait.
  • Classifying listeners by command line would be weaker than exact launchd PID ownership and would interact poorly with supported wrappers.
  • Unconditionally booting out the LaunchAgent before checking the port would add downtime, change the established no-reload path, and still would not make an unrelated owner safe.
  • Polling for a free port cannot establish a stable invariant under 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 restart can 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's KeepAlive behavior.

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 onto e0206b4967a6.

  • The ownership-aware restart guard protects the managed PID, re-reads runtime state after cleanup, and rejects unverifiable ownership before the first write or launchctl mutation.
  • The managed-owner regression models IPv4 and IPv6 listeners owned by launchd PID 4242, verifies cleanup receives { protectedPid: 4242 }, verifies the post-cleanup runtime read, and reaches enable plus kickstart -k.
  • The unrelated-owner regression models a Python listener at PID 5151, verifies the diagnostic error, and proves that the plist is unchanged, no file write occurs, and no enable, bootout, or kickstart mutation is issued.
  • Existing focused tests continue to cover effective-port resolution, repeated --port flags, invalid stored ports, reload/bootstrap behavior, and the no-explicit-port path.
  • git diff --check origin/main...HEAD passes.

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:

The 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 port 19896. It is reproduction/supervisor evidence from proof head 601a25481223, not a claim that the final ownership guard was live-run on the current head.

Before restart, launchd reported PID 68831, and lsof showed that same PID owning both loopback listeners:

$ launchctl print gui/$(id -u)/ai.openclaw.pr89096proof
state = running
pid = 68831

$ lsof -nP -iTCP:19896 -sTCP:LISTEN
node  68831  shawn  ...  TCP 127.0.0.1:19896 (LISTEN)
node  68831  shawn  ...  TCP [::1]:19896 (LISTEN)

The earlier patch reached launchd, which replaced PID 68831 with PID 69148; deep status and health then passed:

$ node openclaw.mjs --profile pr89096proof gateway restart
Restarted LaunchAgent: gui/501/ai.openclaw.pr89096proof

$ node openclaw.mjs --profile pr89096proof gateway status --deep
Runtime: running (pid 69148, state active)
Connectivity probe: ok
Listening: 127.0.0.1:19896, [::1]:19896

$ node openclaw.mjs --profile pr89096proof gateway health --json
{"ok":true,"eventLoop":{"degraded":false,"reasons":[]},"plugins":{"errors":[]}}

That run demonstrates the real KeepAlive/busy-port condition and that kickstart -k performs the intended replacement. The final patch makes the preflight safer than that proof head: it protects PID 68831 from 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.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S labels Jun 1, 2026
@shawnduggan
shawnduggan marked this pull request as ready for review June 1, 2026 13:44
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label Jun 1, 2026
@clawsweeper

clawsweeper Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 20, 2026, 3:45 PM ET / 19:45 UTC.

Summary
The branch permits restartLaunchAgent to continue through launchctl kickstart -k when every busy-port listener is the current LaunchAgent PID, while retaining failure for unknown, mixed, or unrelated owners.

PR surface: Source +20, Tests +84. Total +104 across 2 files.

Reproducibility: yes. The PR supplies a concrete macOS LaunchAgent KeepAlive reproduction, and the exact-head proof exercises both a managed dual-stack listener and an unrelated listener against the changed restart function.

Review metrics: 1 noteworthy metric.

  • Restart ownership states: 1 managed-busy state added; unrelated, mixed, missing-PID, and unverified busy states remain rejected. The change selectively restores a normal LaunchAgent restart without removing the existing port-conflict safety boundary.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Rebase onto current main and rerun the focused macOS LaunchAgent proof against the refreshed merge head.

Risk before merge

  • [P1] This availability-sensitive restart guard should be rebased and rechecked on the current main merge result; a custom wrapper that violates the documented exec contract will fail safely rather than being treated as the managed listener.

Maintainer options:

  1. Refresh and retain the ownership guard (recommended)
    Rebase onto current main, rerun the focused launchd proof, and merge only if the exact-PID managed-owner and unrelated-owner outcomes remain intact.
  2. Pause for a broader lifecycle redesign
    Defer this targeted fix only if maintainers intend to replace the documented LaunchAgent wrapper/PID contract with a different service-identity model.

Next step before merge

  • [P2] No repair job is warranted: the prior unrelated-owner blocker is covered by the revised guard and tests, while rebasing and required-check refresh are routine merge handling.

Security
Cleared: The two-file patch changes no dependencies, permissions, secret handling, CI, artifact execution, or trust boundary; its fail-closed behavior is confined to local LaunchAgent restart classification.

Review details

Best 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 KeepAlive reproduction, and the exact-head proof exercises both a managed dual-stack listener and an unrelated listener against the changed restart function.

Is this the best way to solve the issue?

Yes. Comparing listener PIDs to the freshly read launchctl print PID is narrower and more authoritative than removing the guard or inferring ownership from process command lines; it also preserves failure for unrelated owners.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against e2bb04328f5c.

Label changes

Label justifications:

  • P1: A managed macOS gateway can currently reject its own healthy listener during restart and leave users without the normal service-restart path.
  • merge-risk: 🚨 availability: The patch changes the pre-kickstart ownership decision for a live gateway port, so an incorrect classification could affect restart availability.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The July 20 exact-head macOS proof shows the managed KeepAlive owner is restarted and healthily replaced, while an unrelated owner is rejected with no launchctl or plist mutation; the redacted transcript identifies the exact head and test environment.
  • proof: sufficient: Contributor real behavior proof is sufficient. The July 20 exact-head macOS proof shows the managed KeepAlive owner is restarted and healthily replaced, while an unrelated owner is rejected with no launchctl or plist mutation; the redacted transcript identifies the exact head and test environment.
Evidence reviewed

PR surface:

Source +20, Tests +84. Total +104 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 27 7 +20
Tests 1 118 34 +84
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 145 41 +104

What I checked:

  • Current-main gap: The current-main src/daemon/launchd.ts source does not contain the branch's ownership-specific error or managed-listener exception; the branch therefore still addresses an unmerged restart path. (src/daemon/launchd.ts:1334, e2bb04328f5c)
  • Guard behavior: The branch protects the pre-cleanup LaunchAgent PID, refreshes runtime identity after cleanup, and permits a busy port only when every listener PID equals that current managed PID; all other busy states throw before plist or launchctl mutation. (src/daemon/launchd.ts:1334, 17ffdaac8cce)
  • Regression coverage: Focused tests cover the managed dual-stack PID path reaching enable/kickstart -k and the unrelated-owner path preserving the plist and issuing no service mutation. (src/daemon/launchd.test.ts:2328, 17ffdaac8cce)
  • Feature provenance: The pre-restart stale-listener cleanup originated with the LaunchAgent hardening work, while the later generic busy-port precondition created the behavior this PR narrows; the related merged PRs identify the two ownership paths. (src/daemon/launchd.ts:1334, b405c6e640be)
  • After-fix runtime proof: The July 20 exact-head macOS proof records a KeepAlive LaunchAgent replacing its listener PID after enable/kickstart, and separately verifies an unrelated owner produces no service or plist mutation. (src/daemon/launchd.ts:1334, 17ffdaac8cce)

Likely related people:

  • BunsDev: Merged PR b405c6e added the macOS stop/restart port-verification precondition that this change refines for the managed-listener case. (role: introduced adjacent port-precondition behavior; confidence: high; commits: b405c6e640be; files: src/daemon/launchd.ts, src/daemon/launchd.test.ts)
  • sallyom: Merged PR 8db6fcc added the targeted LaunchAgent stale-gateway cleanup immediately preceding the port check. (role: introduced stale-listener cleanup; confidence: high; commits: 8db6fcca777a; files: src/daemon/launchd.ts, src/daemon/launchd.test.ts)
  • fuller-stack-dev: Merged PR 6f63140 recently maintained the same LaunchAgent port-release and fail-closed lifecycle surface. (role: recent adjacent lifecycle contributor; confidence: medium; commits: 6f6314090226; files: src/daemon/launchd.ts, src/daemon/launchd.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (7 earlier review cycles)
  • reviewed 2026-06-21T10:20:59.001Z sha 601a254 :: needs maintainer review before merge. :: none
  • reviewed 2026-06-30T00:05:52.207Z sha 601a254 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-20T11:32:04.997Z sha ec7dbdc :: needs maintainer review before merge. :: none
  • reviewed 2026-07-20T12:15:05.994Z sha ec7dbdc :: needs maintainer review before merge. :: none
  • reviewed 2026-07-20T12:57:49.588Z sha ec7dbdc :: found issues before merge. :: [P1] Preserve failure behavior for unrelated port owners
  • reviewed 2026-07-20T17:04:22.627Z sha 17ffdaa :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-20T17:46:54.936Z sha 17ffdaa :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 1, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label Jun 1, 2026
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. label Jun 15, 2026
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. labels Jun 21, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 21, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jul 20, 2026
@shawnduggan shawnduggan changed the title [codex] fix launchd restart when gateway port is busy fix: allow macOS gateway restart while LaunchAgent owns its port Jul 20, 2026
@clawsweeper clawsweeper Bot added status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 High-priority user-facing bug, regression, or broken workflow. and removed P2 Normal backlog priority with limited blast radius. labels Jul 20, 2026
@shawnduggan
shawnduggan force-pushed the fix/launchd-restart-busy-port branch from ec7dbdc to ce59324 Compare July 20, 2026 16:53
@shawnduggan
shawnduggan force-pushed the fix/launchd-restart-busy-port branch from ce59324 to 17ffdaa Compare July 20, 2026 17:00

Copy link
Copy Markdown
Contributor Author

Exact-head macOS runtime proof is complete for 17ffdaac8cce928f4d8824be4e74446b7ee2e4b6.

Managed KeepAlive owner

A disposable KeepAlive=true LaunchAgent owned both loopback listeners on port 49188. Before restart, launchd PID 4277 owned 127.0.0.1:49188 and [::1]:49188, and the HTTP health response reported PID 4277.

The exact-head restartLaunchAgent call completed with audited mutations ["enable","kickstart"]. Launchd replaced PID 4277 with PID 4853; both listeners moved to PID 4853, and the replacement health response was {"ok":true,"pid":4853}.

Unrelated port owner

A second disposable KeepAlive=true LaunchAgent remained running at PID 5094, while an unrelated process PID 5113 owned port 49191.

The exact-head restart rejected it with:

gateway port 49191 is busy but is not verifiably owned by LaunchAgent ai.openclaw.pr89096-unrelated-b008b75f

The fail-closed assertions all passed:

  • launchd PID stayed 5094
  • mutation audit stayed empty: []
  • the unrelated listener stayed PID 5113 and remained healthy
  • plist SHA-256 stayed 56612aeaf34fc1bcc5c05d33b5d5ea2572889542866e223da260feeb9f314124
  • plist mtime was unchanged

Both disposable LaunchAgents, listeners, and temporary homes were cleaned up by the harness. The run concluded successfully with managedOwner: "pass" and unrelatedOwner: "pass".

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 21, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jul 21, 2026
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]>
@steipete

steipete commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Maintainer landing proof for final exact head fd3e72b468366a61948cf67576c244cf1541c881.

The final four-file patch fixes both ownership races:

  • restart protects the supervised LaunchAgent PID and re-reads launchd state after cleanup;
  • stale cleanup refreshes the protected PID after lsof enumeration and before filtering/signals, so a KeepAlive replacement cannot be killed as stale;
  • a busy port is accepted only when every listener belongs to the current managed PID;
  • mixed, unrelated, missing-PID, unattributed, or unresolved ownership fails closed before plist writes or launchctl mutation.

Proof completed:

  • Focused restart-stale-pids, launchd, and handoff tests — 178 passed, 2 platform skips.
  • Real launchd integration suite — 5/5 passed with disposable LaunchAgents.
  • Real macOS managed-owner proof — PID 52352 → 52763, exactly two process starts and one requested SIGTERM, demonstrating stale cleanup did not terminate the managed process before kickstart -k.
  • Real unrelated-owner proof — restart rejected with no lifecycle mutation, unchanged plist hash/mtime, managed PID 52933 and unrelated listener PID 52946 both left alive.
  • Blacksmith Testbox focused tests: https://github.com/openclaw/openclaw/actions/runs/29813415018 — 187 passed, 24 platform skips.
  • Blacksmith Testbox core/test typechecks: https://github.com/openclaw/openclaw/actions/runs/29813547552 — passed.
  • Exact-head hosted CI: 80 successful, 44 intentionally skipped, 1 neutral, 2 cancelled automation contexts, no pending or failed checks.
  • Targeted formatting and git diff --check passed.
  • Final Codex autoreview after the PID-race repair: clean, no accepted/actionable findings, overall correct 0.93.
  • Repo-native review artifacts validate as READY FOR /prepare-pr with zero findings.
  • OPENCLAW_TESTBOX=1 scripts/pr prepare-run 89096 passed on the exact final head; the remote PR tree matches the prepared tree.

Known proof gap: mixed and missing-PID listener sets are deterministic unit cases because healthy macOS lsof does not naturally produce those states. They fail closed before mutation.

Contributor credit for @shawnduggan is preserved. Broader SIGTERM and in-process lifecycle work remains tracked separately in #88309.

@steipete

steipete commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Resolved in exact head fd3e72b468366a61948cf67576c244cf1541c881.

Stale cleanup now refreshes the protected launchd PID after listener enumeration and before candidate filtering or signal delivery. A KeepAlive replacement captured by lsof is therefore excluded rather than killed. Deterministic tests exercise the real cleaner enumeration/filter/signal path, including PID replacement, resolver failure, stale-plus-managed listeners, and no unnecessary managed-process termination.

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

clawsweeper Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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.

@steipete
steipete merged commit f53e9f6 into openclaw:main Jul 21, 2026
125 of 127 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 22, 2026
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: L status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants