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>
* fix(infra): enforce maxBytes in body-less HTTP error snippet path
* chore: rebase to trigger CI after boundaries check fix
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(sessions): log warning when parseJsonlEntries skips malformed lines
* fix(sessions): also warn in buildSessionInfo for malformed JSONL lines
* fix(sessions): warn in parseSessionEntries for malformed JSONL lines
* test(sessions): add warning regression tests for parseSessionEntries
* test(sessions): add parseJsonlEntries warning regression test
Add test verifying parseJsonlEntries logs warning for malformed JSONL
lines via loadEntriesFromFile, covering the second of three instrumented
session JSONL readers.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sessions): update parseSessionEntries warning expectations after rebase
* style(sessions): remove unnecessary type assertion in test
* test(sessions): add buildSessionInfo warning regression tests via SessionManager.list
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(clickclack): reply to a top-level message in-channel, not as a new thread
sendClickClackText routed any replyToId to createThreadReply, and the inbound
handler stamps replyToId = <triggering message id> on every reply. As a result
every reply to a top-level channel message opened its own thread, so the main
channel timeline showed nothing and the chat was effectively unusable.
Route a bare replyToId to the main channel as a quote-reply (quoted_message_id)
instead — matching the reply-to affordance of the Discord/Slack/Telegram
channels — and reserve threads for genuine thread context (an explicit threadId
or a thread-kind target). DM replies likewise quote-reply in the same
conversation. quoted_message_id is omitted when there is no reply context, so
plain sends are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(clickclack): cover quote payload typing
---------
Co-authored-by: Marvinthebored <marvinthebored@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(macos): adopt shared read-only chat transcript cache
Cache-first cold open for the macOS chat window/panel: the last known
transcript and session list paint immediately from the shared SQLite
transcript cache, then live gateway history replaces them wholesale.
Recent chats stay browsable read-only while the gateway is unreachable;
sending remains gated by connection state.
- Wires OpenClawChatSQLiteTranscriptCache into WebChatSwiftUIWindowController;
DB at ~/Library/Application Support/OpenClaw/chat-cache.sqlite.
- Gateway identity (MacChatTranscriptCache.gatewayID), derivable offline:
local keys on the canonical gateway state dir; remote/direct keys on the
full canonical URL (scheme, host, resolved port, percent-encoded
path/query); remote/ssh keys on the SSH target plus the resolved remote
gateway port, mirroring the tunnel port resolution. Unconfigured mode
gets no cache.
- macOS file protection: no per-file Data Protection classes; iOS-only
attribute stays gated behind #if os(iOS) in the shared store, and the
per-user container plus FileVault protect at rest.
- Onboarding chat stays uncached (transient guided setup session).
Part of #100194
* feat(macos): wire the offline command outbox into chat windows
* style(macos): fix orphaned doc comment; regenerate docs map
* style(macos): doc-comment lint fix; regenerate docs map on current main