* 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.
* fix: prevent unhandled rejection crash when raw stream write fails
The try/catch wrapper around void appendRegularFile() could not catch
async rejections because the void operator evaluates the Promise to
undefined synchronously. If appendRegularFile rejected (disk full,
permission denied, symlink parent rejected), the unhandled rejection
handler classified it as FATAL and exited the process with code 1.
Replace the broken try/catch with a proper .catch() handler that
correctly ignores raw stream write failures as originally intended.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: preserve sync guard alongside async rejection handler
ClawSweeper review feedback: the outer try/catch guards against
synchronous failures before the promise is returned, while the
.catch() handler catches async rejections. Both layers are
necessary for defense-in-depth.
Add fault-injection test coverage verifying:
- Async rejection (ENOSPC) is contained without process crash
- Synchronous throw from the dependency is caught
- Normal write path works correctly
- Disabled stream is a no-op
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): prove raw stream rejection containment
Punchcard-Session: brisk-cedar-brook-w3
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(ui): make active-run steering reliable
* chore: leave release notes to release prep
* fix(ui): align steer result typing with main
* fix(gateway): reject stale steering owners
* [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>
* fix(google): accept base64url in Live audio
* fix(google): normalize base64url in one pass
* fix(google): normalize base64url at every Google media boundary
* fix(google): normalize image media and avoid double music validation
* test(google): exercise URL-safe base64 fixtures
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* [AI] fix(plugins): warn when registerHook uses a typed hook event name
api.registerHook registers into the legacy internal-hook path, while typed
lifecycle events (before_tool_call, message_received, ...) are dispatched
exclusively by the typed hook runner. Registrations under typed names
silently never fired since #117372 moved the write to legacyInternalHooks.
Emit a registration diagnostic pointing to the public api.on(...) API so
plugin authors see the no-op at load time instead of trusting a false
"loaded".
Related to #116965
Co-Authored-By: deepseek-v4-flash <noreply@anthropic.com>
* [AI] fix(plugins): point registerHook comment at public api.on API
Condense the inline comment in createToolHookRegistrars and replace the
internal registerTypedHook migration reference with the public api.on(...)
API, matching the emitted warning and hooks docs. Behavior and tests
unchanged.
---------
Co-authored-by: deepseek-v4-flash <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* fix(gateway): make doctor dreaming timestamp comparators NaN-safe
compareDreamingEntryByRecency and compareDreamingEntryByPromotion used
`Number.isFinite(aMs) || Number.isFinite(bMs)` then `return bMs - aMs`.
When exactly one side parsed to NaN (non-empty malformed timestamp), the
guard still entered the branch and returned NaN - bMs, producing NaN and
leaving Array.sort order undefined for the doctor.memory.status dreaming
entry list. Coerce unparseable timestamps to -Infinity via a shared
parseDreamingTimestampMs helper and return a finite -1/0/1 from the
timestamp branch so the sort stays deterministic.
* fix(memory-core): incorporate producer-side NaN-safe timestamp comparator
ClawSweeper [P1] finding: gateway-only fix is incomplete because Memory Core
has the same mixed-NaN comparator BEFORE the gateway merges lists. A valid
entry discarded upstream cannot be recovered at the gateway boundary.
Incorporate the producer-side fix:
- Export compareStoreTimestampDesc from short-term-promotion-utils.ts
- Replace the buggy raw Date.parse + bMs-aMs pattern in
short-term-promotion-stats.ts with the NaN-safe compareStoreTimestampDesc
This makes #118749 self-contained: both gateway doctor.ts AND memory-core
stats.ts comparators are NaN-safe. The companion #118750 now only needs to
cover the recency ranking fix (ageDays/recency calculation).
* fix(memory): preserve valid dreaming diagnostics
* style(memory): format dreaming imports
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
The stale plugin config repair report counted heartbeat and
modelByChannel hits per surface but listed all stale channel ids in
parentheses, so a heartbeat entry could claim channel ids that were
never heartbeat targets. List the deduplicated ids of the hits for
each surface instead, matching the other change entries.