* fix(memory-core): fail closed on non-ENOENT companion stat errors
Only treat ENOENT as 'sidecar is absent'. Other fs errors (EACCES, EIO,
ELOOP, EMFILE) now return null (fail-closed) instead of silently treating
the sidecar as zero-byte, preventing legacy data from being dropped on
transient or permission failures.
* fix(memory): silence self-inflicted legacy sidecar migration warning
OpenClaw can leave a zero-byte placeholder at the legacy memory sidecar
path (~/.openclaw/memory/<agentId>.sqlite) while the live memory index
is the per-agent SQLite database. The legacy-index migration check found
the empty file, attached it, saw no legacy schema, and warned on every
CLI invocation and gateway start, with no way for users to clear it.
Treat a demonstrably empty sidecar (zero-byte main file and no WAL or
journal content) as nothing to import: remove it quietly as a migration
change instead of emitting a permanent warning. Sidecars that contain
any bytes still go through the existing import and warning paths, so a
real legacy index or a genuinely unrecognized database is unaffected.
Closes#114626
* fix(memory-core): fail closed on non-ENOENT companion stat errors
Only treat ENOENT as 'sidecar is absent'. Other fs errors (EACCES, EIO,
ELOOP, EMFILE) now return null (fail-closed) instead of silently treating
the sidecar as zero-byte, preventing legacy data from being dropped on
transient or permission failures.
* test(memory-core): add ELOOP and EACCES companion stat regression coverage
Regression tests for the fail-closed fix: when a WAL/SHM/journal
companion's fs.stat fails with ELOOP (self-referential symlink) or
EACCES (permission denied), the migration must NOT remove the main
legacy sidecar file. Only ENOENT means genuinely absent.
Addresses Whatsskill Codex review P1 finding on openclaw/openclaw#114661.
* ci: retrigger after transient runner failures
* origin/main:
fix(sqlite): skip slow-lock diagnostic when busyTimeoutMs is zero (#115990)
fix(canvas): serve Content-Length on Canvas document HEAD responses (#117964)
fix(workboard): diagnose archived cards still in an active status (#116359) (#117290)
feat(diagnostics): configure OTEL metric name prefixes (#116687)
fix(ui): respect UTC when filtering pending usage sessions (#118004)
* [AI] fix(sqlite): skip slow-lock diagnostic when busyTimeoutMs is zero
busyTimeoutMs: 0 means "do not wait for locks, fail immediately".
The slow lock wait threshold should not collapse to 1ms in this case,
causing false-positive WARN logs for normal successful transactions.
Fixes#115972
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sqlite): add regression tests for busyTimeoutMs: 0 threshold
The existing test suite did not cover busyTimeoutMs: 0, which caused
false-positive slow-lock warnings for zero-timeout lease transactions.
Added:
1. 'does not warn for busyTimeoutMs: 0 with fast transactions' —
verifies the regression is fixed (0 → uses 1000ms default threshold)
2. 'still warns for busyTimeoutMs: 0 when crossing 1000ms boundary' —
verifies genuinely slow transactions still trigger the diagnostic
Both tests pass on the patched code (12/12 total).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(workboard): diagnose archived cards still in an active status
An archived card with an active status (ready/running/blocked/etc.) was
silently excluded from dispatch with no signal on any surface —
workboard show rendered it normally, dispatch returned count:0 with empty
startFailures, and computeCardDiagnostics returned [] for archived cards.
Operators could only find it by reading the database directly.
Add an archived_but_active diagnostic kind to WORKBOARD_DIAGNOSTIC_KINDS
and an unarchive action. In computeCardDiagnostics, when a card has
archivedAt set but status is not done, emit the warning so workboard show
and store.diagnostics report it. Done+archived cards stay silent (no
diagnostic) as before. The diagnostic is transient — refreshDiagnostics
still skips archived cards, so their stored metadata is not rewritten.
Fixes#116359
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(workboard): expose archived active cards
* ci: re-trigger after sqlite flip-proof e2e flake
Unrelated to workboard diagnostic changes; sqlite session/transcript
flip-proof e2e failed with array mismatch on an untouched path.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
An unparseable webhookUrl resolves to no path, and the monitor bails before it
creates ingress or registers the HTTP route. The gateway still published the
default path, and because this channel never sets connected, health evaluation
read healthy for the lifetime of the process while inbound delivery was dead.
Make resolveGoogleChatWebhookPath return string | null and have the gateway
publish lifecycle: blocked with lastError instead of a path. The status store
patch-merges, so the blocked branch clears webhookPath explicitly.