Skip to content

fix(launchd): detect profile-scoped updater jobs#96924

Closed
tayoun wants to merge 2 commits into
openclaw:mainfrom
tayoun:fix/update-launchd-self-cleanup
Closed

fix(launchd): detect profile-scoped updater jobs#96924
tayoun wants to merge 2 commits into
openclaw:mainfrom
tayoun:fix/update-launchd-self-cleanup

Conversation

@tayoun

@tayoun tayoun commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

A macOS profile-scoped updater launchd job can get stuck repeatedly rerunning openclaw update --yes.

Observed locally with profile tayoun: launchd job ai.openclaw.tayoun.update.20260625T201026-0400 ran the updater 300 times. The first run upgraded OpenClaw from 2026.6.1 to 2026.6.10; the next 299 runs were no-op updates from 2026.6.10 to 2026.6.10, repeatedly stopping/killing the gateway. The stuck job used the profile-scoped label shape ai.openclaw.<profile>.update.<timestamp>.

The existing launchd updater cleanup/detection parser recognized legacy ai.openclaw.update.* and ai.openclaw.manual-update.<digits> labels, but not profile-scoped updater labels. That meant the update CLI startup cleanup did not disable the current updater job, and status/doctor could not reliably surface the stale profile-scoped update job.

Summary

  • Recognize profile-scoped launchd updater labels such as ai.openclaw.tayoun.update.20260625T201026-0400.
  • Match dotted profile labels that are valid under the existing launchd label contract, such as ai.openclaw.dev.team.update.20260625T201026-0400.
  • Include those jobs in stale updater detection so status/doctor can surface stuck update jobs.
  • Let the update CLI's startup cleanup disable the current profile-scoped updater job instead of leaving KeepAlive free to rerun openclaw update --yes.
  • Preserve the existing guard that avoids disabling a gateway LaunchAgent when the active profile name itself matches the update-label shape.

Evidence

Local incident evidence:

  • update-20260625T201026-0400.log contained 300 Updating OpenClaw entries.
  • The same log contained 300 Update Result: OK entries.
  • The first run showed Before: 2026.6.1 and After: 2026.6.10.
  • Subsequent runs showed Before: 2026.6.10 and After: 2026.6.10.
  • update-20260625T201026-0400.err.log contained 300 gateway restart/kill entries.
  • The stuck label was ai.openclaw.tayoun.update.20260625T201026-0400.

Live macOS launchd proof after this patch:

LABEL=ai.openclaw.dev.team.update.20260626T012120-0400
-- launchctl list before patched cleanup --
39117	0	ai.openclaw.dev.team.update.20260626T012120-0400
-- patched parser sees stale updater job --
{"label":"ai.openclaw.dev.team.update.20260626T012120-0400","pid":39117,"lastExitStatus":0}
-- patched cleanup disables dotted profile updater job --
{"label":"ai.openclaw.dev.team.update.20260626T012120-0400","disabled":true}
-- launchctl print-disabled after patched cleanup --
		"ai.openclaw.dev.team.update.20260626T012120-0400" => disabled

The proof job was a temporary benign user LaunchAgent using /bin/sleep 600 with KeepAlive=true; it was booted out and removed after the proof capture.

Regression coverage added in this PR:

  • Parses ai.openclaw.tayoun.update.20260625T201026-0400 from launchctl list.
  • Parses and disables dotted profile labels such as ai.openclaw.dev.team.update.20260625T201026-0400.
  • Reports profile-scoped updater labels from findStaleOpenClawUpdateLaunchdJobs().
  • Disables a current profile-scoped updater label via disableCurrentOpenClawUpdateLaunchdJob().
  • Keeps the existing safety guard for gateway jobs whose profile-derived label happens to match the profile update-label shape.

Verification run locally:

  • node scripts/run-vitest.mjs src/daemon/launchd.test.ts — 92 passed.
  • pnpm exec oxfmt --check src/daemon/launchd.ts src/daemon/launchd.test.ts.
  • node scripts/run-oxlint.mjs src/daemon/launchd.ts src/daemon/launchd.test.ts.
  • pnpm tsgo:core.
  • git diff --check.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. triage: refactor-only Candidate: refactor/cleanup-only PR without maintainer context. and removed triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 26, 2026
@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 26, 2026, 7:54 PM ET / 23:54 UTC.

Summary
The PR broadens macOS launchd updater label normalization and regression tests to detect and disable profile-scoped updater jobs, including dotted profile labels.

PR surface: Source +8, Tests +69. Total +77 across 2 files.

Reproducibility: yes. source-reproducible: current main rejects the reported profile-scoped updater label before cleanup, and the PR body includes after-fix macOS launchctl output for the same detection and disable path.

Review metrics: none identified.

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

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

Next step before merge

  • [P2] No repair lane is needed because the PR already contains the narrow code/test fix and sufficient macOS live-output proof; the remaining action is maintainer merge review.

Security
Cleared: The diff only broadens internal launchd updater-label parsing and tests; I found no credential, dependency, CI, package, or supply-chain regression.

Review details

Best possible solution:

Land the shared launchd normalizer and focused regression tests after ordinary maintainer merge gating.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: current main rejects the reported profile-scoped updater label before cleanup, and the PR body includes after-fix macOS launchctl output for the same detection and disable path.

Is this the best way to solve the issue?

Yes. The shared launchd normalizer is the narrow owner boundary used by update cleanup, deep status, doctor notes, and explicit stale-job disabling; patching only the update command would leave diagnostics inconsistent.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P1: The PR addresses a macOS updater loop that can repeatedly stop or kill the managed gateway and interrupt active user workflows.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes copied live macOS launchctl output showing the patched helper detects and disables a temporary dotted profile-scoped updater LaunchAgent.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied live macOS launchctl output showing the patched helper detects and disables a temporary dotted profile-scoped updater LaunchAgent.
Evidence reviewed

PR surface:

Source +8, Tests +69. Total +77 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 9 1 +8
Tests 1 70 1 +69
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 79 2 +77

What I checked:

  • Repository policy read: Read the full root AGENTS.md; its OpenClaw PR review, proof, macOS launchd validation, and read-only cleanup guidance affected this review. (AGENTS.md:1, e0932e0bc4e1)
  • Current main misses profile-scoped updater labels: Current main only normalizes legacy ai.openclaw.update.* labels and numeric manual-update labels, so ai.openclaw..update. is rejected before stale-job reporting or current-job cleanup can act on it. (src/daemon/launchd.ts:63, e0932e0bc4e1)
  • PR fixes the shared parser: The PR head adds a full-shape profile-scoped updater regex to the same normalizer used by launchctl-list parsing, stale updater scans, explicit disabling, and current-job disabling. (src/daemon/launchd.ts:54, 53ce1e677ab8)
  • Regression coverage matches the fixed paths: The PR head tests parsing, stale-job reporting, current-job disabling, explicit disabling, dotted profile labels, and the gateway-label guard for profile names that themselves match the update-label shape. (src/daemon/launchd.test.ts:472, 53ce1e677ab8)
  • Updater startup caller checked: The update CLI calls disableCurrentOpenClawUpdateLaunchdJob before mutating updates, so extending the normalizer reaches the reported KeepAlive rerun path before the gateway is stopped or updated. (src/cli/update-cli/update-command.ts:3329, e0932e0bc4e1)
  • Diagnostic callers checked: Deep gateway status and doctor platform notes both consume findStaleOpenClawUpdateLaunchdJobs, so the shared parser change also reaches stale updater diagnostics instead of only the update startup cleanup. (src/cli/daemon-cli/status.gather.ts:608, e0932e0bc4e1)

Likely related people:

  • TurboTheTurtle: Authored the merged manual-update launchd-job recognition work that touched the same normalizer, stale-job scan, status, doctor, and test surfaces. (role: recent adjacent contributor; confidence: high; commits: 49e5091f180e; files: src/daemon/launchd.ts, src/daemon/launchd.test.ts, src/cli/daemon-cli/status.gather.ts)
  • steipete: Introduced the stale updater launchd status/doctor surface and the legacy update launchd self-disarm path that this PR extends to profile-scoped labels. (role: feature history contributor; confidence: high; commits: 7acbb2260c4a, d5b87672f8ad; files: src/daemon/launchd.ts, src/daemon/launchd.test.ts, src/cli/update-cli/update-command.ts)
  • vincentkoc: Recent history includes update-command work on the self-update and managed gateway restart flow where the launchd cleanup runs before update mutation, and the PR is currently assigned to this account. (role: adjacent update-flow contributor; confidence: medium; commits: 62fad3da8606, a192b2ea52b3; files: src/cli/update-cli/update-command.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.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 26, 2026
@tayoun

tayoun commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed both blockers from the last review:

  • widened profile-scoped updater matching to the same dotted label character set accepted by the existing launchd label contract
  • added dotted profile regression coverage for ai.openclaw.dev.team.update.<timestamp> detection and disable paths
  • added live macOS launchctl proof to the PR body showing a temporary dotted profile-scoped updater LaunchAgent detected by the patched helper and disabled via launchctl print-disabled

Local verification passed:

  • node scripts/run-vitest.mjs src/daemon/launchd.test.ts — 92 passed
  • pnpm exec oxfmt --check src/daemon/launchd.ts src/daemon/launchd.test.ts
  • node scripts/run-oxlint.mjs src/daemon/launchd.ts src/daemon/launchd.test.ts
  • pnpm tsgo:core
  • git diff --check

@clawsweeper

clawsweeper Bot commented Jun 26, 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.

@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 26, 2026
@vincentkoc vincentkoc self-assigned this Jun 26, 2026
@vincentkoc
vincentkoc force-pushed the fix/update-launchd-self-cleanup branch from 0ab61c7 to 53ce1e6 Compare June 26, 2026 23:50
@vincentkoc

Copy link
Copy Markdown
Member

closing this as unsafe to land in its current shape. the observed ai.openclaw.tayoun.update.<timestamp> label is not a new product concept; it is a profile-derived launchd label shape. however, launchctl list exposes only the label, and this matcher cannot distinguish an updater from a legitimate gateway whose profile itself is tayoun.update.<timestamp> (or the dotted equivalent). stale status/doctor output would then recommend disabling a live gateway. the current-profile guard only protects the process being inspected, not another active gateway profile. a replacement needs launchd job metadata/program-arguments discrimination or an explicit updater marker before broadening stale-job classification.

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

Labels

gateway Gateway runtime P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. triage: refactor-only Candidate: refactor/cleanup-only PR without maintainer context.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants