Commit Graph

565 Commits

Author SHA1 Message Date
Peter Steinberger e68e973c05 fix: report unknown gateway service state when inspection fails (#125734)
* fix(daemon): preserve unknown service load state

* test(daemon): update service load-state fixtures

* fix(onboard): preserve loaded service diagnostic

* fix(daemon): preserve unknown lifecycle state

* fix(status): preserve loaded JSON fields

* fix(daemon): fail restart on unknown service state

* fix(daemon): fence unknown service state
2026-08-18 09:20:02 -07:00
Peter Steinberger 2d3aaaa0d5 feat(node-host): support Cloudflare Access service tokens (#125193)
* feat(node-host): support Cloudflare Access service tokens

Carry SecretRef-backed, origin-bound Cloudflare Access credentials across node onboarding, gateway and worker connections, and node-owned worker transfers. Refs #125112.

* chore: align changed gate after rebase

* test(node-host): keep access proof within lint budgets

* style(node-host): keep supervisor within line budget

* fix(node-host): omit absent access config

* fix(node-host): reject Access credentials on plaintext routes

* fix(node-host): reject plaintext before ref resolution

* test: cover Access log redaction over TLS
2026-08-17 04:12:27 -07:00
Peter Steinberger a917c99e92 fix(runtime): classify Node releases consistently across install and launch (#124812)
* fix(runtime): align Node release version guards

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* test(runtime): include Node version helper in source fixture

* fix(install): align Node release checks across boundaries

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* fix: keep node version guard legacy-compatible

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* test(runtime): exercise legacy launcher preflight

* fix(installer): validate installed Node release versions

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* fix(installer): compare Node version parts numerically

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* test(installer): cover 17-digit Node major

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-17 00:42:23 -07:00
Peter Steinberger 67ea9c2281 fix(cli): replace stale service selectors on profile switch (#124790)
* fix(cli): replace stale service profile selectors

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* refactor(cli): own service identity checks with daemon constants

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* fix(update): preserve managed home selector ownership

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* test(update): keep selector coverage within limits

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-16 23:00:08 -07:00
Peter Steinberger e6a14c0855 test: replace assertion chains with typed fixture builders — wave 3 (#124986)
* test: replace assertion chains with typed fixture builders — wave 3

* test: keep policy fixture type local

* test: type gateway context fixture

* test: type systemd stream fixture

* test: type chat pane fixture overrides
2026-08-16 21:49:21 -07:00
Peter Steinberger 568b920b21 feat(lint): enforce import ordering and deduplication (#124730)
* refactor(imports): dedupe and hoist imports

* feat(lint): enforce import/no-duplicates and import/first
2026-08-16 11:44:52 -07:00
Peter Steinberger 5e8350fb04 fix: report stopped LaunchAgents as stopped (#123961)
* fix(daemon): report stopped launch agents as stopped

* fix(daemon): uninstall stopped launch agents cleanly
2026-08-14 20:10:23 -07:00
Peter Steinberger 5ed9bc2bd2 fix: gateway restart avoids false failure when systemd probe is unavailable (#123069)
* fix(cli): avoid false gateway restart failures

Allow restart to proceed when the loaded-state probe is unavailable but an installed service definition identifies the native manager. Native restart errors and Gateway health checks remain authoritative.

* fix(daemon): detect system-scope restart definitions

Move installed-definition detection into the platform service adapter so gateway restart can recover from an unavailable loaded-state probe for both user- and system-scope systemd units.
2026-08-13 02:04:25 -07:00
Peter Steinberger c23d66e3b5 refactor: consolidate coercion ownership (#122692)
* refactor: consolidate coercion ownership

* test: align shard check with weighted planning

* chore: refresh plugin SDK API baseline
2026-08-12 09:25:28 -07:00
Peter Steinberger 3d76246792 refactor: eliminate final export name collisions (#122083)
* refactor: resolve final export name collisions

* refactor: update remaining collision rename consumers

* style: format rebased auth helpers

* test: update remaining session entry mocks

* test: update remaining runtime mock exports

* test: update delivery info path mock

* refactor: reconcile combined collision sweeps

* chore: regenerate collision and sdk baselines
2026-08-11 11:18:24 -07:00
Peter Steinberger f88f078cd2 fix(daemon): require validated Node runtime (#122070)
Remove program-argument runtime rediscovery so managed Gateway and node-host services consume only the canonical validated Node path, while executable wrappers remain supported.\n\nRefs #122061.
2026-08-11 08:32:06 -07:00
Peter Steinberger e390781534 refactor: burn cross-directory export name collisions (#121893)
* refactor: name subsystem logger exports

* refactor(test): distinguish exported test doubles

* refactor: consolidate canonical owner helpers

* refactor: give cross-domain helpers distinct names

* chore(lint): ratchet collision debt baselines

* fix(test): complete collision rename consumers

* fix(test): update remaining collision mock consumers

* fix(test): update transcript reader mock export

* refactor: keep embedded logger name at its owner

* fix(test): align embedded logger mock with owner

* refactor: name shared assistant phase extraction

* fix(ui): update assistant phase extractor import

* chore(generated): refresh collision and SDK baselines

* style(test): format merged plugin mocks

* chore(sdk): refresh API content hashes
2026-08-11 06:50:22 -07:00
Peter Steinberger 8327504c84 refactor(agents): split systemd into concept modules (#122002) 2026-08-11 04:09:49 -07:00
Peter Steinberger 3d707a9b96 refactor(daemon): split launchd into concept modules (#121929)
* refactor(agents): split launchd into concept modules

* fix(daemon): restore launchd service environment type

* refactor(daemon): keep launchd probe result internal
2026-08-11 01:49:23 -07:00
Peter Steinberger bcf342903f refactor: finish export shadow hazard renames (#121471)
* refactor(gateway): distinguish pending node handlers

* refactor(approvals): distinguish button presentation builder

* refactor(daemon): distinguish service home resolver
2026-08-10 01:01:20 -07:00
Peter Steinberger 7713e8fb74 fix(update): prevent Gateway respawn during package replacement (#103537)
* fix(update): quiesce respawn-pending services

* fix(update): preserve disabled launch agents
2026-08-09 21:23:50 -07:00
Peter Steinberger 0dbdf994b3 feat(macos): isolate named app profiles (#121136)
* feat(macos): isolate named app profiles

* refactor(macos): isolate profile launch ownership

* fix(macos): avoid overlapping approvals socket access

* fix(macos): declare profile defaults concurrency ownership

* fix(macos): return profiled node launch arguments

* chore(i18n): refresh macOS profile source inventory

* fix(macos): gate profile startup before services

* test(macos): evaluate profile state before assertions

* fix(daemon): skip absent launchd deactivation

* fix(macos): fail closed on profile port conflicts

* chore(i18n): refresh profile conflict inventory

* fix(macos): ignore non-gateway launch agent claims

* test(macos): stabilize profile lifecycle timing

* fix(macos): remove stale dashboard URL

* chore(macos): refresh native source baseline
2026-08-09 14:50:15 -07:00
Peter Steinberger 5d98d2e6ec fix(uninstall): preserve user data when gateway removal fails (#113887)
* fix(uninstall): gate cleanup on service teardown

* fix(daemon): scope strict task removal checks
2026-08-09 04:40:50 -07:00
Peter Steinberger e70e43fadc fix: prevent LaunchAgent reinstall from leaving gateway down (#120699)
* fix(daemon): restore launchd install failures

* style: format launchd regression tests

* fix(daemon): guard cached launchd installs

* chore: leave changelog to release flow
2026-08-08 15:23:47 -07:00
Peter Steinberger e6353d85ef fix(daemon): drop stale service version metadata (#120702) 2026-08-08 14:14:22 -07:00
Peter Steinberger da4a656cdb improve: doctor migration checks no longer load every bundled plugin runtime (#120678)
* perf(plugins): declare doctor contract surfaces

* perf(doctor): slim migration import closures

* perf(plugins): narrow doctor declaration record surface and wire owner-test lane

Registry records carry only the doctorContract declaration instead of the whole
parsed manifest, and check:changed now selects the src/plugins-owned declaration
honesty and closure-guard tests for extension module/manifest changes so
cross-lane drift cannot pass PR classification.

* fix(doctor): keep control-plane dist imports require-safe

Keep doctor and channel control-plane chunks off exec-class dependencies, and enforce native require(esm) loading during postbuild.

* chore(plugin-sdk): regenerate API baseline

* chore(plugin-sdk): sync export ordering

* fix(plugins): satisfy doctor contract CI boundaries

* perf(doctor): make qqbot doctor closure dependency-light

qqbot was the last plugin above 5s in doctor state-migration enumeration
(~8s under tsx/jiti). The cost was not the state-key builder (already a
leaf): its doctor closure value-imported the runtime-doctor SDK barrel,
whose plugin-state-store/state-db re-exports pull kysely (~330 modules),
plus security-runtime for one fileExists (~200 modules), all resolved
per-module by jiti during enumeration.

Split the migration-define helpers and light re-exports into a new
private-local plugin-sdk/runtime-doctor-migrations subpath; runtime-doctor
re-exports it so its public surface is byte-identical (API baseline hash
unchanged). qqbot's doctor-contract and state-migrations now import only
the light subpath, swapping fileExists for the equivalent async
legacyStateFileExists already in the closure.

qqbot enumeration: ~8.0s/531 modules -> ~0.25s/18 modules.

* chore(plugin-sdk): drop private-local subpath from API baseline

runtime-doctor-migrations is private-local-only; the baseline tracks public
modules, and the earlier line was generated before the classification.

* fix(plugins): register runtime-doctor-migrations boundary paths

The private-local subpath list feeds the extension package boundary map;
the shared paths config and xai's derived overrides must carry the same
entry or the boundary contract test fails.
2026-08-08 13:29:18 -07:00
Peter Steinberger eecbfcc960 fix(infra): unify env-truthiness, missing-path, realpath, and abort-sleep semantics (#120359)
* fix(infra): unify environment truthiness

* fix(infra): unify missing path classification

* refactor(infra): unify realpath fallbacks

* fix(infra): unify abortable sleep errors

* docs(infra): clarify path fallback semantics

* fix(infra): route realpaths through policy wrapper

* fix(infra): ratchet plugin SDK wildcard budget

* fix(agents): preserve zero-delay abort precedence

* fix(infra): preserve fallback and media recovery contracts

* fix(plugins): share quarantine path resolution
2026-08-08 10:58:57 -07:00
Peter Steinberger fb61ae15fc fix(daemon): skip unreadable foreign LaunchDaemon plists in ownership scan (#120481)
* fix(daemon): skip unreadable foreign LaunchDaemon plists in ownership scan

inspectSystemLaunchDaemonOwnership raised SystemLaunchDaemonOwnershipError when any /Library/LaunchDaemons plist was unreadable, such as third-party VPN helper plists with mode 600. This broke gateway install/repair and launchd.integration.e2e on affected Macs. Unreadable plists are foreign by contract, while loaded same-label daemons remain caught by the bracketing launchctl probes.

* fix(ci): regenerate plugin-sdk api baseline after 267268f646

* test(doctor): scope gateway-bind persistence test to its writer seam

Culprit: 267268f646.

* docs(daemon): document unreadable plist tradeoff
2026-08-08 00:48:59 -07:00
Sally O'Malley 4cbbfc2159 fix(systemd): apply dotenv changes on gateway restart (#119441)
* fix(systemd): load state dotenv at gateway startup

Signed-off-by: sallyom <somalley@redhat.com>

* fix(systemd): refresh managed dotenv values on restart

Signed-off-by: sallyom <somalley@redhat.com>

* fix(gateway): clear removed managed dotenv values

Signed-off-by: sallyom <somalley@redhat.com>

* fix(dotenv): canonicalize managed override keys

Signed-off-by: sallyom <somalley@redhat.com>

* fix(secrets): preserve providerless env refs

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
2026-08-07 10:04:04 -04:00
Peter Steinberger a672065d5e fix(daemon): legacy systemd unit removal reloads the user manager and stops masking failures (#120027)
* fix(daemon): reload systemd user manager after removing legacy units

uninstallLegacySystemdUnits removed unit files without a daemon-reload, so the
user manager kept the deleted unit definitions loaded and startable until an
unrelated reload. Legacy-unit removal now reuses the shared
disable-or-tolerate-missing helper, surfaces non-ENOENT unlink failures instead
of swallowing them, and reloads the user manager once after any unit file was
removed (matching uninstallUserSystemdGatewayUnit).

* chore: re-fire CI

* chore: re-fire CI against fixed main baseline
2026-08-07 01:17:58 -07:00
Chris Allbritton 7d039fb424 fix: gateway restart fails and leaves the macOS LaunchAgent unloaded when the gateway drains active work (#116875)
* fix(daemon): retry launchd bootstrap while a booted-out job tears down

launchctl bootout returns once launchd accepts the request, not once the
job has exited, so the reload path could bootstrap into a label the
outgoing gateway still held and fail with EIO. bootout had already
deregistered the job, leaving KeepAlive with nothing to respawn and the
gateway silently down until a manual bootstrap.

Retry bootstrap while the teardown is still pending, bounded by the
plist ExitTimeOut, and restore the job if bootstrap still fails.

* fix(daemon): report an unrestored LaunchAgent and scope the bootstrap retry

The reload path swallowed a failed restore, so a bootstrap that failed after
bootout surfaced only the original error while the LaunchAgent stayed booted
out. Combine the restore failure into the thrown error and say plainly that
the job is gone and KeepAlive has nothing left to respawn.

The teardown retry also applied to every bootstrapLaunchAgentOrThrow caller.
launchd answers the same EIO for a label that is merely still registered, so
start, install, and recovery paths burned the full teardown deadline on an
already-loaded service before failing anyway. Gate the retry on callers that
just issued bootout, and defer to the existing isLaunchctlAlreadyLoaded model
instead of treating that response as a teardown to wait out.
2026-08-06 20:21:36 -07:00
SunnyShu 21e9634f10 fix(node-cli): warn when systemd user lingering is disabled after install (#118430)
* [AI] fix(node-cli): warn when systemd user lingering is disabled after install

openclaw node install now detects when systemd user lingering is off and
warns the operator (text + JSON) to run 'sudo loginctl enable-linger <user>'.
Without lingering, the user-level node service is torn down when the last SSH
session ends, so the node silently goes offline after logout.

The check is read-only and never auto-enables lingering, matching the
operator-consent policy used elsewhere. It runs only on the verified-success
path: an optional onVerified hook is added to installDaemonServiceAndEmit
that fires after service.isLoaded() confirms the service is loaded and before
the success payload is emitted. The linger diagnostic runs there, so a failed
install or verification failure never carries a linger warning (avoids
misdirecting the operator to fix lingering for a service that was not
successfully installed). The already-installed short-circuit warns separately.
Skipped on non-Linux and when systemd user service is unavailable.

Adds unit tests for both paths, the linger=yes no-op, the install-failure
isolation, the verification-failure no-warn regression, and the
systemd-unavailable skip, plus response.test.ts cases covering onVerified
running on success and failing safely when it throws. The
readSystemdUserLingerStatus mock is typed with the full linger union to
satisfy tsgo. Documents the linger step in docs/cli/node.md and
docs/nodes/troubleshooting.md.

Real-behavior evidence captured on a Linux host by toggling
loginctl disable-linger/enable-linger and running the real install flow:
linger=no emits the warning on successful install (text + JSON) and on the
already-installed path; linger=yes emits nothing; a failed install or
verification failure emits no warning.

Fixes #107033

Co-Authored-By: deepseek-v4-flash <noreply@anthropic.com>

* fix(node-cli): align linger user with service owner

* docs(node): narrow crash-loop claim to gateway units

The duplicate-scope guard that raises on two managers running the same unit
name is enforced for gateway units (two supervisors on the same port SIGTERM
each other in a restart loop); assertNoSystemGatewayOwnership returns early
for node services, so claiming node services crash-loop misattributes gateway
behavior. Qualify the troubleshooting note accordingly.

Addresses ClawSweeper P3 finding on PR #118430.

* fix(systemd): align linger checks with service owner

* test(doctor): align linger status mock contract

* style(doctor): format linger mock

* test(wizard): mock systemd service account

---------

Co-authored-by: deepseek-v4-flash <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-06 19:28:10 -07:00
Peter Steinberger c663c39763 refactor(test): table-drive exact-duplicate test blocks, batch 2 (#120071)
* refactor(test): table-drive tooling test cases

* refactor(test): table-drive agent test cases

* refactor(test): table-drive core test cases

* refactor(test): table-drive plugin test cases

* refactor(test): table-drive package test cases

* chore(test): prune max-lines baseline

* fix(test): preserve table row literal types
2026-08-06 17:00:28 -07:00
Peter Steinberger 36554c2820 refactor(daemon): consolidate managed service lifecycle ownership (#119647) 2026-08-05 09:49:12 -07:00
Peter Steinberger 2692954c72 refactor(infra): consolidate service paths and usage rollups (#119433) 2026-08-04 20:39:53 -07:00
Jason (Json) ae21f3c00c fix(daemon): skip unlabeled launchd plists (#119091) 2026-08-04 01:31:25 -06:00
Peter Steinberger d512d1b182 fix(gateway): preserve trusted CA certificates when reinstalling services (#117944) 2026-08-02 03:19:00 -07:00
Peter Steinberger f620c19a93 fix(gateway): bound service reads in status (#117636)
* fix(gateway): bound status service reads

* fix(gateway): preserve service probe errors
2026-08-01 14:38:40 -07:00
Masato Hoshino 29577fb03b fix(daemon): keep backslashes and quotes intact in generated systemd units (#117375)
* fix(daemon): keep backslashes and quotes intact in generated systemd units

systemdEscapeArg escaped only pairs of backslashes and rendered a quote as
two backslashes plus a quote, so any value holding a lone backslash or a
quote did not survive the readers in this module -- or systemd itself. The
installed Linux service received a different value than the operator
configured, with nothing reporting the difference.

parseSystemdEnvAssignment also carried a private copy of the shared
unquoting loop whose escape branch compared one character against a
two-character literal, so it never unescaped anything. Service inspection
reads through that path, so status and doctor reported the escaped form.

Align the writer with serializeSystemdEnvironmentFileValue and route the
reader through the shared splitter its three siblings already use. Adds
round-trip tables mirroring the Windows cmd sibling.

* fix(daemon): align systemd round-trip parser with current main

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-01 12:04:05 -05:00
Peter Steinberger ae1ab269eb fix(systemd): parse all inline environment assignments (#117484) 2026-08-01 09:09:51 -07:00
Sasan 6938f7dddb fix: allow gateway service commands for named profiles (#116314)
* fix: gateway service commands refuse a named profile or relocated OPENCLAW_HOME

- Resolve the default install identity against the canonical state directory
  for the active OpenClaw home and profile instead of the unprofiled OS
  account default.
- `--profile <name>` / `--dev` project `.openclaw-<profile>` state and config
  paths, so every named profile was classified as isolated state and refused
  `install`, `start`, `stop`, `restart`, `uninstall`, Doctor service repair,
  and self-update service handling.
- `OPENCLAW_HOME` relocates all OpenClaw path defaults and is documented for
  running as a dedicated service user; a relocated home is now an install
  identity. `HOME` alone still is not.
- An `OPENCLAW_STATE_DIR` or `OPENCLAW_CONFIG_PATH` pointing outside those
  canonical paths is still treated as isolated state.
- Recovery guidance in the refusal message now names the paths that must match.

Verified: focused vitest shards for the changed suites plus the daemon, CLI,
and doctor suites that consume the identity check; tsgo core and core-test
lanes; oxlint; docs format, MDX, link, and map checks.

* fix(gateway): keep relocated homes isolated

* fix(config): validate service profile identity

* fix(daemon): enforce named-profile service ownership

* fix(update): reject drifted service selectors before probes

* test(windows): prove scheduled task lifecycle

* test(windows): harden scheduled task proof cleanup

* test(windows): bind lifecycle proof to checkout

* test(windows): normalize cleanup exit status

* test(windows): verify effective task privilege

* test(windows): protect scheduled task proof roots

* test(windows): prove listener-owned task lifecycle

* test(windows): fix scheduled task proof contracts

* test(windows): remove redundant mock coercions

* test(windows): measure fallback before task probes

* test(windows): prove scheduled task process origin

* fix(gateway): preserve unmanaged restart fallback

* test(gateway): cover denied restart ownership

* test(gateway): keep restart helper types private

* test(gateway): classify lifecycle helpers as test code

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-08-01 11:28:39 +08:00
Peter Steinberger 08c47c29bb fix(daemon): report platform service removal failures (#116819)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 05:33:39 -07:00
Peter Steinberger deee9ed3cb fix(daemon): verify service starts and installations (#116815)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
2026-07-31 05:32:06 -07:00
Ben Badejo 847dcad4a4 fix(gateway): avoid false port-busy reports behind Tailscale Serve (#116579)
* fix(gateway): scope lifecycle port probes to bind host

* fix(gateway): honor configured bind in lifecycle probes

* fix(gateway): preserve restart health call compatibility

* fix(gateway): scope daemon port ownership

---------

Co-authored-by: Benjamin Badejo <ben@benbadejo.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-07-30 21:49:25 -07:00
Vincent Koc b015925bc3 test(core): remove dead and duplicate coverage (#116641)
* test(markdown): make code table style coverage assertive

* test(core): remove duplicate regression coverage
2026-07-31 11:48:46 +08:00
Miorbnli 5e1e70e423 fix(systemd): clear start-limit latch before gateway start (#116178)
* fix(systemd): clear start-limit latch before gateway start

systemd latches a unit into failed/start-limit-hit after a crash loop,
then refuses systemctl start until reset-failed clears the latch. The
reset-failed step only ran for the restart action, so the natural
recovery command `openclaw gateway start` failed on a crash-looped
gateway while `openclaw gateway restart` recovered it.

Run reset-failed before both start and restart (stop is unchanged),
mirroring launchd's startLaunchAgent which calls launchctl enable before
kickstart. reset-failed is idempotent and a no-op on a healthy unit.

* test(systemd): cover start-limit latch clearing on system-scope start

Mirrors the existing system-scope restart test: when startSystemdService
targets a system unit as root, it must emit reset-failed before start so
a crash-looped gateway recovers via `openclaw gateway start` too.

* test(systemd): assert healthy start tolerates reset-failed no-op

reset-failed is idempotent on a non-failed unit (systemd returns success
without changing unit state), so prepending it to start cannot regress a
healthy gateway. Covers the merge-risk concern that every start now
depends on an extra reset-failed call.

* test(systemd): cover reset failure and stop controls

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-07-30 12:47:56 +08:00
Vincent Koc d5e3c68632 fix(daemon): block conflicting systemd gateway owners (#116162) 2026-07-30 09:53:38 +08:00
Ayaan Zaidi d8074d7ca2 fix(daemon): require system-scope ownership before removing the user unit
Doctor's dueling-scope repair removed the user-scope gateway unit on file
presence alone. A system unit file that is merely staged, disabled, failed, or
uncheckable would then cost the operator their only working gateway, and the
default confirm value is true so non-interactive repair amplifies it.

Gate the removal on isSystemUnitActiveOrEnabled: the system unit must be the
live or boot-configured supervisor. Otherwise doctor explains the ambiguity and
prints both fix directions instead of deleting anything. An erroring probe
fails closed for the same reason.

Applies the ClawSweeper rank-up move on #91221.
2026-07-30 08:52:05 +09:00
北京刻熵科技有限责任公司 dcfd6a535d fix(daemon): only treat same-canonical-name user+system units as dueling
Address review: a marker-owned system unit with a *different* name (e.g. an
intentional rescue-bot gateway on the same host) must not be classified as a
duplicate of the canonical user unit, or doctor could remove a legitimate user
gateway. Dueling now requires user.unitName === system.unitName; the direct
canonical system path still matches the real #79375 case. Adds a regression
test for "user canonical + custom marker-owned system" -> kind:"user".
2026-07-30 08:52:05 +09:00
北京刻熵科技有限责任公司 9308557f72 fix(daemon): detect and resolve dueling user+system systemd gateway units
After upgrading on Linux, a user-scope unit
(~/.config/systemd/user/openclaw-gateway.service) and a system-scope unit
(/etc/systemd/system/openclaw-gateway.service) can both exist and both try to
manage the gateway. They bind the same port and each instance's stale-process
detection SIGTERMs the other, producing an endless restart cascade (#79375).

The detector `findInstalledSystemdGatewayScope` checked the user path first and
returned early, so it could never surface that both scopes coexist.

This adds, in three layers:

1. Detection: `findSystemdGatewayInstallation` — a discriminated union
   (none/user/system/dueling) that reports every installed scope without
   early-returning. `findInstalledSystemdGatewayScope` is refactored to delegate
   to it while preserving the exact user-first preference its four lifecycle
   callers rely on (no behavior change for stop/restart/is-enabled/runtime).
   Adds `uninstallUserSystemdGatewayUnit` (removes only the $HOME user unit, no
   root needed) and the pure `formatDuelingScopesWarning` helper.

2. Doctor: `maybeResolveDuelingSystemdGatewayScopes` detects the dueling state
   and, after the existing confirm/policy gate, removes the redundant user-scope
   unit while keeping the root-installed system unit authoritative. Declining or
   an externally-managed policy falls back to the existing cleanup hints.

3. Startup guard: in service mode, when a stale-kill actually happened, log a
   targeted remediation pointing at `openclaw doctor --fix` instead of letting
   the loop look like routine stale cleanup. Diagnostic only — the kill decision
   is unchanged.
2026-07-30 08:52:05 +09:00
Peter Steinberger e80fe942c8 fix(gateway): stop start-time repair from retargeting managed services (#115935) 2026-07-29 13:41:19 -04:00
Harjoth Khara 895b691c55 fix(daemon): refuse duplicate launchd gateway managers (#97285)
* fix(daemon): block duplicate launchd owners

Co-authored-by: Harjoth Khara <harjoth.khara@gmail.com>

* fix(ci): satisfy launchd docs and lint gates

* fix(ci): remove unused launchd exports

* docs: refresh gateway map

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-29 13:31:22 -04:00
Peter Steinberger 383f8947c1 fix: doctor skips host services for isolated state (#115922)
* fix(doctor): isolate host service management

* fix(doctor): clarify service isolation recovery

* test(doctor): isolate service identity fixtures

* test(daemon): keep lifecycle fixtures lint-clean

* test(daemon): isolate install identity fixtures
2026-07-29 11:09:56 -04:00
Vincent Koc f6e874835c fix(gateway): stop managed crash loops for pending agent DB migrations (#115631)
* fix(gateway): recover managed services after agent DB migration

* test(infra): use managed temp directory cleanup
2026-07-29 19:21:46 +08:00
Peter Steinberger aa1e8683d8 fix(doctor): keep cleanup hints scoped to detected services (#115559) 2026-07-29 01:08:19 -04:00