* fix: SQLite WAL file can stay inflated on a running gateway until restart
Since #82366 switched the periodic 30-minute checkpoint to PASSIVE (to keep
WAL maintenance off the event loop), no checkpoint on a running process
truncates the WAL *file* any more -- only close() does, i.e. a restart.
wal_autocheckpoint recycles WAL space in place but never shrinks the file,
and is itself a PASSIVE checkpoint a reader can transiently block. So when a
reader briefly pins frames (e.g. a memory reindex, a backup, a slow query),
the WAL grows past the autocheckpoint size and then stays parked at that
high-water mark for the whole life of the process. Observed in production: a
1.6 GB agent DB left a 1.6 GB -wal that only manual TRUNCATE checkpoints
could reclaim. This affects every SQLite-backed store (task registry, plugin
state, proxy capture, memory host, ...), not just memory.
Set PRAGMA journal_size_limit (default 64 MiB, overridable via
journalSizeLimitBytes) right after wal_autocheckpoint so any completing
checkpoint -- including the PASSIVE periodic/auto ones #82366 now relies on
-- truncates the WAL file back to the ceiling. This restores the bounded
on-disk WAL that TRUNCATE used to give, without reintroducing the blocking
checkpoint #82366 removed: journal_size_limit only changes how far a
completing checkpoint truncates, never checkpoint timing. The 64 MiB ceiling
sits ~16x above the autocheckpoint steady state (~4 MB at 1000 pages), so it
is inert in normal operation and engages only on pathological growth.
Related: #82366, #81715
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: tighten SQLite WAL ceiling proof
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Audit findings, dangerous-flag labels, exec-approval provenance, filesystem
policy drift, and the skill-workshop tool diagnostic printed agents.list.*
paths users cannot paste into openclaw.json: dotted agents.list.<id> is valid
in no shape, and index-keyed agents.list.<n> only names the internal
validation projection. All display surfaces now emit agents.entries.<id>;
the summary agent-id extractor regex follows the new labels. Roster-aware
builders keep an indexed fallback only for id-less malformed legacy rows,
and validation/doctor/legacy-migration paths intentionally keep the legacy
form because they point into the user's actual file. Follow-up deferred
from #113160.
* fix(release): terminate Windows package command trees
* fix(release): type package runner test output
* fix(release): scope package runner normalization to Windows
* test(release): run package timeout proof on Windows CI
---------
Co-authored-by: TheAngryPit <16145902+TheAngryPit@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(gateway): retain root-work admission for streamed HTTP runs
Streaming requests to /v1/chat/completions and /v1/responses dispatch the
agent run from a detached `void (async () => {...})()` that intentionally
outlives the HTTP handler. The handler itself runs inside
runWithGatewayHttpWorkAdmission, which releases its root-work admission as
soon as it returns.
Because the detached run inherits that same AsyncLocalStorage store, it ends
up holding a *released* lease. isGatewaySubordinateWorkAdmissionClosed()
returns `current.released` for an inherited store, so every subordinate
session/lane admission the run subsequently requests is refused with
GatewayDrainingError - on a gateway that is not draining at all.
Symptoms: streamed turns fail partway with "Gateway is draining; new tasks
are not accepted", surfacing to OpenAI-compatible clients as a generic error
chunk. Non-streaming requests are unaffected, because they await the run
inside the handler's still-live admission.
Fix: retain the admission across the handler boundary with
retainGatewayRootWorkAdmissionContinuation() - the helper that already
exists for exactly this case ("Transfers an admitted request root to work
that intentionally outlives its handler") - and release it in the existing
finally block so drain accounting stays balanced.
* test(gateway): cover streamed HTTP admission lifetime
* test(gateway): satisfy deferred timer lint
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>