* fix(memory-core): guard qmd mcporter JSON.parse against non-JSON stdout
runQmdSearchViaMcporter parsed mcporter subprocess stdout with JSON.parse
outside the runMcporter try/catch (qmd-manager.ts:2722). A non-JSON stdout
(daemon warning bleeding onto stdout, output truncated by maxOutputChars, CLI
killed early, or flag mismatch) threw a raw SyntaxError that propagated
uncaught out of runQmdSearchViaMcporter, surfacing in agent logs as a
context-free SyntaxError with no hint of the actual mcporter failure.
Wrap JSON.parse in try/catch and throw a typed Error carrying a stdout snippet
(matching the guard pattern already used in parseListedCollections in this
file, and the recent matrix #97973 / sms #97999 / signal #98073 /
telegram-ingress #98372 JSON.parse guard series).
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): preserve cause in qmd mcporter JSON.parse guard
Add { cause: err } to the re-thrown Error to satisfy the preserve-caught-error
lint rule; the original SyntaxError is now chained, improving diagnosability.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): redact raw stdout from qmd mcporter error (security-boundary)
ClawSweeper flagged that the previous error message exposed raw mcporter
stdout (first 200 chars) before session visibility filtering, which could
leak sensitive content. Drop the stdout preview from the thrown message;
keep the original SyntaxError as `cause` for diagnostics so the parse-failure
reason is still reachable without surfacing unfiltered subprocess output.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): keep qmd mcporter error message generic (no raw stdout leak)
The SyntaxError thrown by JSON.parse embeds a snippet of the raw input in its
message (e.g. Unexpected token '<', then the raw bytes). Including that
SyntaxError message in the thrown Error would surface unfiltered mcporter
stdout before session visibility filtering. Drop the parse-error message from
the thrown Error; keep the original SyntaxError as cause for developer
diagnostics only.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(memory-core): keep qmd mcporter cause generic (no raw stdout leak via formatErrorMessage)
formatErrorMessage walks the .cause chain into the user-visible path.
Keeping the JSON.parse SyntaxError on .cause leaked its embedded raw
stdout snippet through formatErrorMessage even with a generic message.
Give the cause a generic message too; the raw snippet no longer reaches
the user.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(build): fall back to tsx for build TypeScript scripts
* fix(build): use tsx across TypeScript build scripts
---------
Co-authored-by: Steffen Moeller <moeller@debian.org>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
Adds the middle tier between capture-off and autonomous capture: when
autonomy is disabled, detected durable-instruction signals record a
one-shot pendingSkillSuggestion on the session entry (signal-hash
fingerprint prevents transcript-history replay), and the next
non-heartbeat turn atomically consumes it and injects one bounded
user-role line offering to save the skill. The agent offers, the user
decides; skill_workshop approval flow unchanged; no new config.
Gateways that crash-loop under systemd/launchd previously flapped forever
with no persisted state and no supervisor signal. The gateway now records
every boot outcome in the shared state DB (gateway_boot_lifecycle); three
unclean boots within five minutes trip a breaker that boots the gateway in
safe mode: full control plane available, channel/provider auto-start
suppressed at the channel-manager seam (startup, config hot-reload,
secrets.reload) with manual channels.start override, one stability bundle
per trip. The breaker re-evaluates each boot and logs recovery when the
window drains. Slow shutdowns record forced_stop and never count as
crashes; /readyz stays ready and reports suppressed channels; the health
monitor treats suppressed accounts as expected-stopped. Fatal invalid-config
errors now exit 78 (EX_CONFIG) on both the startup and unhandled-rejection
paths, engaging the systemd unit's pre-existing RestartPreventExitStatus=78
so supervisors stop relaunching until the config is fixed.
The batch output file download path creates a readline interface over a
Readable.fromWeb() response body stream. If JSON.parse throws on a malformed
JSONL line, the for-await loop exits via exception but the readline interface
and underlying Readable stream were never explicitly closed or destroyed,
leaving the HTTP response body stream dangling.
Extract the stream reading into , a testable helper
that wraps the iteration in a try-finally so both reader.close() and
inputStream.destroy() are always called, matching the pattern established in
#98493 for the same class of leak.
* [AI] fix(agents): detect legacy openai-codex provider in model-not-found hint
In buildMissingProviderModelRegistrationHint, add an early-return check
for the legacy openai-codex alias (via normalizeProviderId). Instead
of suggesting a models.providers[] config entry that the config
validator rejects without baseUrl, the hint now points operators to
run openclaw doctor --fix for migration or check provider auth.
Fixes#100066
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): cover legacy Codex provider config hint
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
* feat(skills): diagnose skill_workshop hidden by tool policy (#87570)
Workshop can be enabled and auto-capturing while tools.profile hides the
skill_workshop tool; every inspection surface looked healthy. plugins
inspect and openclaw doctor now name the excluding policy layer (global/
agent/provider profile, allowlist, denylist) and the exact alsoAllow
grant to add, via a shared resolveSkillWorkshopToolPolicyAvailability
helper that /learn's guard now reuses instead of composing policy
itself. Diagnosis only; no policy behavior change.
* ci: retrigger
* style: restore exec approval e2e formatting
* fix(install): trap SIGINT so Ctrl+C exits cleanly during upgrade doctor
Three changes to fix the install script's Ctrl+C handling:
1. Add INT/TERM signal traps that clean up temp files and exit with
the correct signal exit codes (130 for SIGINT, 143 for SIGTERM).
2. Preserve signal exit codes (>128) through run_quiet_step so the
doctor path can distinguish user cancellation from normal errors.
Non-signal failures still return 1, preserving existing caller
semantics for all other installer steps.
3. Fix guardCancel in onboard-helpers.ts: exit(0) changed to exit(1)
so Clack prompt cancellation (Escape/Ctrl+C) is treated as failure,
not success. This prevents the installer from continuing with plugin
updates after the user explicitly cancelled.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* fix(install): abort dashboard launch on doctor cancellation
When a user cancels the interactive upgrade-doctor prompt (Clack
cancellation exits 1, SIGINT exits 130), clear should_open_dashboard
so the installer does not launch a dead dashboard after an incomplete
upgrade.
Also propagate non-zero exit from run_doctor() so the non-interactive
upgrade path correctly skips dashboard launch on failure.
* fix: guard every run_doctor caller and add focused tests
The existing-config path called run_doctor without checking its return
value, so a failed or cancelled doctor would still launch the dashboard.
Now both run_doctor call sites guard the return value with if-then.
Adds focused tests verifying: every run_doctor caller is guarded,
dashboard flag is cleared on doctor failure, signal exit codes
propagate through run_quiet_step, and SIGINT (exit 130) triggers
abort_install_int.
* retrigger proof check
* fix: exit 130 on Clack cancellation so installer treats it as SIGINT
guardCancel now exits with 130 (SIGINT convention) instead of 1. When
the user presses Ctrl+C at an interactive doctor prompt, the installer
sees doctor_exit=130 and calls abort_install_int, aborting cleanly
instead of continuing after exit 1.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* fix: narrow exit 130 to doctor-prompter path only
Revert guardCancel to exit 0 by default (matching main) and pass
exit code 130 only from doctor-prompter where the installer needs
to distinguish user cancellation from normal failures.
This preserves the existing cancellation behavior for configure,
wizard, gateway, and daemon prompts while keeping the SIGINT
convention for the installer's doctor subprocess.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
---------
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): serialize background PTT lifecycle
Follow-up hardening for #99840 after the initial fix landed in #100483.
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* docs(changelog): note Android PTT lifecycle hardening
* fix(android): order PTT ownership transitions
* test(android): opt in to coroutine scheduler API
* fix(android): serialize voice capture ownership
* test(android): drive PTT ordering with test dispatcher
* test(android): isolate PTT ordering preferences
* test(android): control PTT invoke dispatchers
* test(android): isolate PTT coroutine dispatchers
* test(android): use current Robolectric resolver API
* test(android): register speech service without deprecated APIs
* test(android): control stale PTT cleanup dispatch
* test(android): isolate voice mode reassertion
* fix(android): clean voice capture on permission loss
* fix(android): invalidate PTT before cancel wait
* test(android): preserve starts queued after cancel
* fix(android): keep one-shot PTT retries idempotent
---------
Co-authored-by: xialonglee <li.xialong@xydigit.com>
* fix(webchat): keep context indicator visible with stale token data
The context usage indicator was disappearing after sending a message because
totalTokensFresh was set to false during the run, even though we had valid
token count data from before the message was sent.
Changes:
- Modified getContextNoticeViewModel to show the indicator even when
totalTokensFresh is false, as long as totalTokens is non-zero
- Added isStale flag to indicate when token data is not fresh
- Applied subtle visual styling for stale data (lighter colors, 5% opacity)
- Added "(updating)" suffix to the percentage text and title when stale
- Updated tests to verify the new behavior
Fixes#89662
* test(ui): cover stale context indicator behavior
* docs(changelog): note Control UI fix
---------
Co-authored-by: 黑承亮0668000844 <bladin@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
new Date(parsed.timestamp).getTime() can return NaN for unparseable
strings. Without a guard, NaN silently propagates into downstream
usage/cost calculations and corrupts billing data.
Add Number.isNaN(timestamp) check, falling back to 0 (same default
as the path when no timestamp key is present).
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>