* fix(hooks): flag hook event names that no trigger site emits
* docs(hooks): clarify bare family subscriptions in unknown-event note
* fix(hooks): word unknown-event diagnostics around core-emitted keys
* fix(hooks): apply unknown-event advisory to legacy config handlers too
* improve(health): surface dead-lettered delivery queue entries
Deliveries that exhaust retries are moved to the failed status in the
SQLite delivery queue for diagnostics, but no health surface ever read
them back: openclaw health stayed all-green while messages sat
dead-lettered, visible only in gateway logs. Add a per-queue failed
count accessor and report dead-lettered entries in the health snapshot
(JSON field plus a warning line), following the existing plugin and
context-engine health section pattern. Observer-only: no retry, purge,
or behavior change.
* test(health): cover snapshot and CLI dead-letter reporting
Add getHealthSnapshot coverage against an isolated state dir, an
openclaw health text-output test for the warning line, exact
oldest-failure timestamp assertions via fake timers, and a debug log
when the delivery-queue health read fails.
* fix(health): recompute dead-letter counts for cached gateway responses
The gateway health handler can serve a cached snapshot for up to a
refresh interval, so a delivery dead-lettered after the cache was
filled stayed hidden from openclaw health. Recompute the delivery
queue summary in mergeCachedHealthRuntimeState alongside the existing
context-engine and model-pricing live merges, and cover the cached
handler path with a regression test.
* fix(usage-bar): bound template file cache to prevent unbounded watcher growth
Add MAX_CACHED_TEMPLATE_FILES=64 limit; evict the oldest entry (closing
its fs.watch watcher) before allocating a watcher for a new key when
the cache is full.
Eviction runs before watcher allocation so we never create a watcher
only to close it immediately. Eviction triggers only when inserting a
new key (!fileCache.has(path)) — retries for an existing key must
not evict other entries.
Fixes#98960
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(test): replace mutable dir variable with cleanup-stack pattern in template.test.ts
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(test): add curly braces for eslint curly rule
Co-Authored-By: Claude <noreply@anthropic.com>
* test(usage-bar): register watcher proof cleanup
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(transcripts): handle read stream errors gracefully in TranscriptsStore
* proof(transcripts): add real behavior proof script for store stream error catch
* fix(transcripts): reject with Error in stream error handler
* proof(transcripts): replace wrapper with real EISDIR stream error proof
* fix(transcripts): reject non-ENOENT stream errors after stream close
* fix(feishu): bound Feishu API JSON response reads to prevent OOM
Replace the bare `response.json()` in `fetchFeishuJson`
(`extensions/feishu/src/app-registration.ts`) with a `readResponseWithLimit`
call capped at 16 MiB. A misconfigured or adversarial Feishu endpoint that
streams an unbounded body previously had no defence; the bounded reader now
cancels the stream at the cap and throws a labelled `feishu.api` error.
Tests: over-cap (32 MiB stream, no Content-Length — stream cancelled, error
matches feishu.api), under-cap (chunked valid JSON — parsed and returned),
and malformed-JSON (labelled feishu.api error). All five tests pass.
* fix(feishu): delegate JSON reads to provider helper
* test(feishu): add real node:http server proof for readProviderJsonResponse bound
* test(feishu): fix lint errors in real HTTP server proof (curly + no-promise-executor-return)
* test(feishu): prove bound reads through SSRF guard
* fix(feishu): satisfy overloaded LookupFn type in hermetic test lookup stub
* fix(mistral): correct model id typo in usesReasoningEffort helper
* chore: re-trigger CI
* test(mistral): cover medium reasoning effort
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The markdown link regex character class [^)\s>]+ stopped at the first
closing paren, truncating URLs like wikipedia.org/wiki/URL_(disambiguation)
at the opening (. Replace with a balanced-parens pattern that supports
one level of parenthetical groups inside the URL path.
Add trimUnbalancedTrailingParens for bare URLs in prose where a trailing
) belongs to surrounding punctuation rather than the URL itself.
* fix(tools-manager): replace spawnSync extraction with safe extractArchive API
Replace the synchronous spawnSync-based archive extraction (unzip/tar)
with the safe extractArchive from @openclaw/fs-safe, which enforces:
- maxArchiveBytes: 100 MB (prevents oversized compressed input)
- maxExtractedBytes: 500 MB (prevents decompression bomb OOM)
- maxEntries: 1000 (prevents zip bomb from exhausting inodes)
- timeoutMs: 60,000 (prevents hung extraction)
- Path traversal and symlink/hardlink protection (built-in)
Removes 5 functions (formatSpawnFailure, runExtractionCommand,
extractTarGzArchive, getWindowsTarCommand, extractZipArchive) and their
associated imports, replacing them with a single async wrapper around
the existing @openclaw/fs-safe infrastructure (re-exported from
src/infra/archive.ts).
Net: -83 lines, + security boundaries across all platforms.
* fix(tools-manager): add download-stream byte cap and regression tests
Add a maxBytes parameter to downloadFile that checks Content-Length
before reading the body and enforces a streaming byte cap during
transfer, so oversized archives are rejected before hitting disk.
Extract MAX_ARCHIVE_BYTES as a module-level constant shared between
downloadFile and extractArchiveSafe, ensuring both gates use the same
100 MB limit.
Add two regression tests:
- rejects downloads with Content-Length exceeding the archive byte cap
- accepts downloads with Content-Length under the archive byte cap
Ref. https://github.com/openclaw/openclaw/pull/98988
* fix(tools-manager): replace PassThrough with Transform stream limiter
- Replace PassThrough data listener with Transform that rejects overflow
chunks *before* they are forwarded to the file pipeline, preventing the
offending chunk from landing on disk.
- Add completion guard and cleanup of partial downloads on failure so
downloadTool does not leave a partial archive on disk.
- Add real behavior proof: test output and live fd/rg download and
extraction through the safe extractArchive code path.
🦞 diamond lobster: L2 evidence (real function calls + real objects)
Ref. https://github.com/openclaw/openclaw/pull/98988
* fix(tools-manager): replace PassThrough with Transform stream limiter
Uses a Transform to reject overflow chunks *before* they are forwarded
to the file pipeline (a PassThrough data listener acts *after* emission
and cannot prevent the offending chunk from landing on disk).
Wraps the pipeline in a completion-guarded block so that partial
downloads are removed when the transfer fails mid-way.
Ref. https://github.com/openclaw/openclaw/pull/98988
* fix(agents): harden helper archive downloads
* fix(agents): preserve archive extraction cause
---------
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(infra): include update timeout in managed-service handoff parent exit wait
The handoff parent exit grace was hard-coded to 60s regardless of
the overall update timeout. When the gateway is draining active
tasks, 60s is often not enough, causing
managed-service-handoff-parent-timeout.
Use the existing timeoutMs parameter as a floor for the parent
exit wait so callers can extend it for long-running drains.
Refs #99666
* fix(infra): add shutdown reserve to managed-service handoff parent exit wait
* fix(infra): align update handoff with restart drain
Co-authored-by: 徐闻涵0668001344 <xu.wenhan1@xydigit.com>
* fix(infra): arm restart with managed handoff
* fix(infra): use live restart drain for auto-update
* fix(infra): force process exit for managed updates
* fix(infra): normalize managed handoff restart delay
* test(infra): cover handoff drain argument
* fix(gateway): upgrade accepted restarts for managed updates
* fix(gateway): bound managed update restart upgrades
* test(gateway): stabilize managed update handoff proof
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): prefer current attempt terminal state
Co-authored-by: weiqinl <liu.weiqin@xydigit.com>
* test(agents): model current attempt terminal state
---------
Co-authored-by: weiqinl <liu.weiqin@xydigit.com>
* fix(agents): keep background exec exit notifications UTF-16 safe at the snippet cut
Background exec exit notifications truncate the command's tail output with a
raw String.slice, which can split a UTF-16 surrogate pair when an emoji lands
at the cut. Use the existing truncateUtf16Safe helper so the message delivered
to the channel never ends with a dangling surrogate half.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(agents): protect exec notify tail boundaries
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(agents): protect node exec follow-up tails
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(agents): type node exec follow-up assertions
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(agents): keep bounded exec output Unicode-safe
* docs(changelog): note exec output Unicode safety
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>