For 323985f4ca (Val Alexander/@BunsDev): adds a Control UI/exports
entry covering the sidebar-trigger affordance alignment across the
resizable divider, mobile layout, and exported-HTML transcript template.
The other Val/@BunsDev fix (b1c515270e) was already covered by the
existing "Control UI/mobile: persist mobile chat settings" entry.
The rest of the last 24h's missing-CHANGELOG candidates are either:
- already covered by adjacent entries (Shakker manifest auth-evidence
series under "CLI/models: keep manifest auth-evidence credentials
visible", Discord application id + Cloudflare 429 under "Channels/
Discord: cool down Cloudflare/Error 1015 HTML 429", config patch
follow-ups under "Plugins/runtime-deps: add openclaw plugins deps",
etc.);
- internal/test/CI/refactor with no operator surface;
- Clawsweeper-bot self-fixes for already-merged PRs;
- Peter-only with no external collaborator (per the
attribution rule against thanking @steipete).
* fix(models): block stale openai-codex/gpt-5.4-mini inline entries via unconditional suppression (#74451)
Suppress explicitly user-configured openai-codex/gpt-5.4-mini inline entries
so a stale models config written by `openclaw doctor --fix` cannot bypass the
manifest capability block and cause repeated assistant-turn failures when the
runtime switches to that model on ChatGPT-backed Codex accounts.
Adds `unconditionalOnly` flag to `buildManifestBuiltInModelSuppressionResolver`
and a `shouldUnconditionallySuppress` helper. Inside `resolveExplicitModelWithRegistry`,
inline matches are now gated on unconditional suppressions (no `when` clause)
before returning. Conditional suppressions such as the qwen Coding Plan endpoint
guard remain bypassable by explicit user configuration, preserving the existing
`resolves explicitly configured qwen3.6-plus before Coding Plan built-in suppression`
behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(changelog): add missing reporter attribution for #74451 models suppression fix
* docs: credit codex mini suppression contributors
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Shakker <shakkerdroid@gmail.com>
* fix(voice-call): close in-flight limiter fail-open on empty remote address
The webhook in-flight limiter (createWebhookInFlightLimiter in
src/plugin-sdk/webhook-request-guards.ts) returns true unconditionally
when tryAcquire is called with an empty key — that is its by-contract
fail-open path used to mean 'caller is opting out of the limiter'.
The voice-call webhook handler reached that path silently: it computed
'req.socket.remoteAddress ?? ""' and passed the empty string straight
into tryAcquire. Whenever req.socket.remoteAddress was absent (closed
socket, edge proxy quirk), the limiter became a no-op and the request
proceeded directly to readBody without any concurrency cap.
Fix: when remoteAddress is missing, log a warning and fall back to a
constant non-empty key ('__voice_call_no_remote__') so all such
requests share one in-flight bucket instead of bypassing the limiter
entirely. The bucket size stays maxInFlightPerKey (default 8), which
is the right defense-in-depth posture against slow-body attacks
arriving with stripped IP info.
Scoped to voice-call only. Other consumers of the SDK helper
(bluebubbles via openclaw/plugin-sdk/webhook-ingress) are not changed
to avoid drive-by edits to plugins this PR does not own. The shared
SDK contract (empty key = bypass) is left as-is and documented
implicitly by the fix's comment block.
The existing 8-concurrent test in webhook.test.ts continues to assert
the limiter engages on the happy path; no new test added since the
private handleRequest path is not unit-test exposed and the change is
two-line auditable from the diff alone.
* test(voice-call): cover missing webhook remote address limiter
* test: align changed package sdk routing
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Tighten Google Vertex ADC manifest evidence to canonical project env vars and canonical ADC fallback paths only.
Local proof:
- OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/agents/model-auth.profiles.test.ts src/plugins/manifest-registry.test.ts src/secrets/provider-env-vars.dynamic.test.ts
- pnpm exec oxfmt --check --threads=1 docs/plugins/manifest.md extensions/google/openclaw.plugin.json src/agents/model-auth-env.ts src/agents/model-auth.profiles.test.ts src/plugins/manifest.ts
- git diff --check origin/main...HEAD
CI note: checks-node-core-support-boundary was red on an unrelated tooling assertion in test/scripts/test-projects.test.ts for packages/sdk/src/index.test.ts routing; that file and scripts/test-projects.mjs are unchanged from origin/main.
* fix(pi-embedded): strip [tool calls omitted] from user-facing text
The internal replay placeholder '[tool calls omitted]' was leaking
into channel output (e.g. Telegram) after aborted tool calls.
Fix: strip the placeholder early in sanitizeUserFacingText so all
channels are protected by default. The replay transcript path in
turns.ts is unaffected — it uses the placeholder internally.
Fixes#74573.
Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
* fix(pi-embedded): preserve whitespace when stripping placeholder
* test(pi-embedded): document replay placeholder sanitization
* fix(pi-embedded): strip consecutive replay placeholders
---------
Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Adds the focused MCP/process/tool-execution CodeQL security shard and documents it in CI docs.
Proof:
- Branch CodeQL security run https://github.com/openclaw/openclaw/actions/runs/25132942030 passed on 9d8ca2bae7.
- New mcp-process-tool-boundary analysis 1200250367 returned 0 results.
- Branch open CodeQL alerts: none.
- Workflow Sanity, Blacksmith Testbox, Blacksmith Build Artifacts Testbox, and OpenGrep PR Diff passed.
Remove the maintainer PAT fallback from the ClawSweeper dispatch workflow so missing app auth fails closed instead of attributing downstream automation to a human token.
Fail setup-code generation when gateway.remote.url is configured but malformed, instead of falling back to a bind-derived URL and issuing a bootstrap token.
Let route-question searches match people-routing metadata from natural-language prompts, and allow wiki_apply evidence provenance fields that the markdown parser already supports.
Adds six missing entries for commits that landed without their own
CHANGELOG.md update, picked from the last six hours of origin/main and
attributed to the original contributors.
Changes:
- Control UI/i18n locale registry expansion + new docs glossaries
(297f4c6e60, 0126692bf5 by @vincentkoc).
- Gateway/diagnostics opt-in startup timeline (097eed8cd8, d001c3436b,
e69da9d578 by @shakkernerd).
Fixes:
- Matrix `verify confirm-sas` cross-signing close (86956f71e6 by
@nklock; #74542).
- `openclaw status` channel context-window overrides (eb7d89f4b9 by
@HemantSudarshan).
- Sandbox Docker daemon graceful when sandbox mode is off (2dadc82cf4
by @kaseonedge; #73671).
- Control UI mobile chat settings persisted via Lit state (b1c515270e
by @BunsDev).
Skipped Peter-only commits with no external collaborator (per the
maintainer-attribution rule against thanking @steipete) and the model
list auth-index series (already covered by the existing "Models/UI:
hide unauthenticated providers" entry).
* fix: improve error message in optimizeImageToJpeg to include actual error details
* fix: improve error message to include configured input for Model does not support images
* fix(media): surface vision pipeline diagnostics
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(matrix): close owner-side device verification loop on SAS confirm
After SAS confirm via the `openclaw matrix verify confirm-sas` CLI, the
operator's Element X stayed in "Verifying…" because three things on the
bot side did not happen before the verb returned:
1. confirmVerificationSas didn't await the rust-crypto verifier promise.
`Verifier.verify()` resolves only after both sides exchange MACs and
the protocol fully settles, including cross-signing-key uploads
triggered by `crossSignDevice`. Returning early meant Element X's
next /keys/query saw an inconsistent state and the prompt persisted.
2. The 30s auto-confirm path (used when the operator initiates from
their phone) explicitly passed `{ trustOwnDevice: false }`, so the
bot never cross-signed its own device on this path. The check inside
trustOwnDeviceAfterConfirmedSas already gates on isSelfVerification,
so flipping the flag is safe — non-self requests remain a no-op.
3. The standalone `confirmMatrixVerificationSas` action did not call
`trustOwnIdentityAfterSelfVerification` (only the higher-level
`runMatrixSelfVerification` path did). Without that call, the bot
had not signed the operator's master key, so Element X had no path
to clear the prompt without a passive sync tick.
Three additive edits:
- verification-manager.ts (confirmVerificationSas): await
session.verifyPromise after confirmSasForSession returns.
verifyPromise is the .then().catch() chain set by
ensureVerificationStarted, which already routes rejections into
session.error, so awaiting it cannot double-throw.
- verification-manager.ts (maybeAutoConfirmSas): pass
{ trustOwnDevice: true } so the auto-confirm path also cross-signs
the bot device for self-verifications.
- actions/verification.ts (confirmMatrixVerificationSas): mirror the
trustOwnIdentityAfterSelfVerification call from
completeMatrixSelfVerification when the returned summary indicates
isSelfVerification.
Tests:
- verification-manager.test.ts: flipped the existing "auto-confirmed
self-verification" assertion (now expects trustOwnDeviceAfterSas to
be called); added two new tests for verifyPromise await and
rejection-on-summary.error.
- actions/verification.test.ts: two new tests asserting
confirmMatrixVerificationSas calls trustOwnIdentityAfterSelfVerification
on self-verifications and not on remote verifications.
Verified end-to-end against matrix.thepolycule.ca (Synapse 1.145.0+ess.1,
MAS-fronted): after `verify confirm-sas`, Element X's device-list view
shows the bot device with a green shield and no pending Verify prompt.
* fix(matrix): guard owner trust after failed SAS verification
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(security): resolve model aliases before audit classification
Before classification, model strings are now resolved through the alias
index so that configured aliases (e.g. 'gpt-prev') are translated to
their canonical provider/key form (e.g. 'openai/gpt-5.4') before hygene
and tier checks run.
Fixes#74455.
Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
* fix(security): share audit model alias resolution
---------
Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Move the mobile chat settings dropdown open state into Lit-owned app state.
- Render the dropdown open class and ARIA disclosure attributes from state.
- Add Escape, outside pointer, tab-change cleanup, and focus restoration.
- Cover closed/open render state and mounted app dismissal flows with browser tests.
Validation:
- pnpm test ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/navigation.browser.test.ts
- pnpm exec oxfmt --check --threads=1 ui/src/ui/app.ts ui/src/ui/app-view-state.ts ui/src/ui/app-render.helpers.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/navigation.browser.test.ts
- node scripts/run-oxlint.mjs --tsconfig tsconfig.oxlint.core.json ui/src/ui/app.ts ui/src/ui/app-view-state.ts ui/src/ui/app-render.helpers.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/navigation.browser.test.ts
Previously, models from unconfigured providers were shown with an
"auth missing" hint, flooding the picker with 900+ unusable entries.
Now addModelSelectOption early-returns when the provider has no auth,
so only usable models appear in /models and the web chat dropdown.
Fixes#74423
* feat(security): add GHSA detector-review pipeline and OpenGrep CI workflows [AI-assisted]
Stand up an end-to-end pipeline that turns every published openclaw GitHub
Security Advisory into a reusable OpenGrep rule, and wire the compiled rules
into manual-dispatch GitHub Actions workflows that publish SARIF to GitHub
Code Scanning.
The pipeline is harness-agnostic: any coding-agent CLI (Rovo Dev, Claude
Code, Codex, OpenCode, or anything you can shell out to) can drive it via
the runner script's --harness flag. Built-in adapters cover the four common
harnesses; --harness-cmd '<template>' supports anything else with shell-style
{prompt}/{model}/{output_file} substitution.
Pipeline pieces:
- scripts/run-ghsa-detector-review-batch.mjs runs your chosen coding harness
in parallel against every advisory using the agent-agnostic detector-review
spec at security/detector-review/detector-review-spec.md. Each case
produces an opengrep general-rule.yml (precise) and broad-rule.yml
(review-aid), plus a coverage-validated report against the vulnerable
commit's changed files.
- scripts/compile-opengrep-rules.mjs walks a run directory, rewrites each
rule's id to ghsa-detector.<ghsa>.<orig-id>, injects ghsa/advisory-url/
detector-bucket/source-rule-id metadata, and uses opengrep itself to drop
rules with InvalidRuleSchemaError so the published super-configs load
cleanly.
Compiled outputs:
- security/opengrep/precise.yml (336 rules)
- security/opengrep/broad.yml (459 rules)
- security/opengrep/compile-manifest.json (per-rule provenance map)
CI workflows (manual workflow_dispatch only):
- .github/workflows/opengrep-precise.yml
- .github/workflows/opengrep-broad.yml
Both install a pinned opengrep, run opengrep scan against src/, upload SARIF
to Code Scanning under categories opengrep-precise / opengrep-broad, and use
continue-on-error: true so findings never block the workflow.
Detector-review spec and assets:
- security/detector-review/detector-review-spec.md the agent-agnostic spec
the runner injects into each per-case prompt
- security/detector-review/references/{detector-rubric,report-template}.md
- security/detector-review/scripts/init_case.py
- security/prompt-suffix-coverage-first.md mandatory prompt addendum that
enforces coverage-first validation (rule must catch the OG vuln, not just
pass synthetic fixtures)
Docs:
- security/README.md end-to-end flow, supported harnesses, regen recipe
- security/opengrep/README.md compiled-config details + recompile recipe
* security: tighten GHSA OpenGrep detector workflow
* chore: refine precise opengrep workflow
* chore: remove stale opengrep metadata
* fix: harden GHSA OpenGrep workflow
* ci: split OpenGrep diff and full scans
* chore: remove performance-only opengrep rule
* ci: use OpenGrep installer path
* chore: enforce opengrep rule metadata provenance
* chore: generalize opengrep rule compilation
* docs: align opengrep rulepack guidance
* chore: support generic opengrep rule sources
* fix: validate opengrep rulepack-only changes
---------
Co-authored-by: Jesse Merhi <security-engineering@atlassian.com>
* feat(nvidia): add NVIDIA provider with onboarding flow
Add the NVIDIA build.nvidia.com API as a bundled provider. Default model
is nvidia/nvidia/nemotron-3-super-120b-a12b: first segment is the provider
id, remaining "nvidia/nemotron-3-super-120b-a12b" is the literal upstream
model id (which happens to start with "nvidia/" because NVIDIA is also the
model maker).
Supporting core change: introduce a provider capability flag
nativeIdsIncludeProviderPrefix so providers whose native catalog ids
intentionally include their provider prefix (OpenRouter) opt into self-prefix
dedupe in modelKey, without hardcoding provider names in core. Providers
whose ids merely happen to start with their own name (NVIDIA) leave the flag
unset and get the full <provider>/<model-id> concatenation.
- extensions/nvidia/*: new plugin, catalog, onboarding, tests, docs
- extensions/openrouter/index.ts: declare nativeIdsIncludeProviderPrefix
- src/plugins/types.ts: add field to ProviderPlugin
- src/plugins/registry.ts: populate self-prefix set on registration
- src/agents/provider-self-prefix.ts: sync accessor used by modelKey
- src/agents/model-ref-shared.ts: modelKey consults the flag
- test updates for affected surfaces
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(model-picker): simplify literal-prefix display to label-only
* fix(model-picker): pass workspaceDir/env to allowlist literal-prefix resolution
* chore: untrack generated baseline JSON artifacts (gitignored)
* fix(nvidia): show literal model ref in picker and onboarding notes
* fix(nvidia): show hint whenever display label differs from stored config
* fix(nvidia): drop redundant hint from Keep current label
* fix(nvidia): restore literal double-prefix display labels
* fix(picker): handle literal-prefix fast path
* fix(picker): show literal keep label
* fix(docs): update nvidia provider docs
* fix(nvidia): update test helper imports
* fix(changelog): add nvidia provider entry
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Prevent hidden channel lifecycle runs from staying stuck as running
Hidden channel-routed runs were dropping session keys on lifecycle events at
our shared agent-event bus. Gateway lifecycle persistence then had to rely on
run-context lookup surviving until the terminal event, which is unnecessarily
fragile for the exact sessions that are intentionally hidden from Control UI.
This keeps session keys on hidden lifecycle events only, preserving the existing
privacy boundary for assistant/tool traffic while making terminal session-state
persistence explicit and test-covered.
Constraint: Hidden channel runs must stay out of Control UI chat/tool streams
Rejected: Broaden sessionKey preservation to every hidden event | would expose more hidden traffic than needed
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: If hidden-run event redaction changes again, keep lifecycle persistence independent from ephemeral run-context lookup
Tested: pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/infra/agent-events.ts src/infra/agent-events.test.ts; pnpm tsgo:core; pnpm tsgo:extensions; pnpm tsgo:core:test; pnpm tsgo:extensions:test; pnpm test src/infra/agent-events.test.ts; pnpm test src/gateway/server-chat.agent-events.test.ts; pnpm test src/gateway/session-lifecycle-state.test.ts; pnpm lint:extensions:bundled; codex exec review returned ship it
Not-tested: Live gateway reproduction against Knox's local stuck-session install
* Clarify hidden lifecycle redaction and cover context fallback
The follow-up review asked for two things: document why the separate error
stream stays redacted for hidden runs, and cover the registered-context fallback
branch for hidden lifecycle events when callers omit sessionKey.
Constraint: Hidden assistant/tool/error diagnostics must remain redacted from Control UI
Rejected: Preserve sessionKey on the generic error stream | terminal persistence already flows through lifecycle phase:error, so widening the visible identity surface is unnecessary
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep hidden-run identity exceptions tightly scoped to terminal lifecycle persistence unless a concrete downstream consumer requires more
Tested: pnpm exec oxfmt --write --threads=1 src/infra/agent-events.ts src/infra/agent-events.test.ts; pnpm test src/infra/agent-events.test.ts; pnpm test src/gateway/server-chat.agent-events.test.ts; pnpm test src/gateway/session-lifecycle-state.test.ts
Not-tested: Full repo gate rerun; previous branch-wide gates remain from the parent PR commit
* fix(gateway): keep hidden agent broadcasts redacted
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
`shouldRemoveRuntimeDepsLock` previously trusted `isAlive(owner.pid)`
alone when deciding whether a lock could be reclaimed. That works fine
on a normal host: when the writer dies the PID is gone and `isAlive`
returns false. Inside Docker it does not — every Node gateway process
runs as PID 1 (or PID 7 with `init: true`) in its container PID
namespace, so a stale lock left behind by a previous incarnation looks
"alive" to the new one. The 5-minute lock-wait timeout then fires and
the supervisor restarts, and the cycle repeats indefinitely. Operators
have to manually remove `.openclaw-runtime-deps.lock` to recover.
This change records `pidStartTimeMs` alongside `pid` and `createdAtMs`
when the lock is acquired, and consults it in the staleness check.
When both sides have start-time evidence and they disagree, the lock
is treated as stale; otherwise the existing PID-alive-means-fresh
behavior is preserved exactly. The capture point uses
`Date.now() - process.uptime() * 1000` once at module load, and the
read side uses `/proc/<pid>/stat` field 22 on Linux (returning null
elsewhere so legacy semantics still apply on macOS/Windows hosts).
This is strictly additive on the wire format and the predicate:
existing lock files without `pidStartTimeMs` continue to take the same
code path they did before, and platforms that cannot resolve a live
PID's start-time fall back to the same legacy behavior.
Refs #74346.
Make the topbar OpenClaw breadcrumb a semantic Overview link, wire the existing navigate event at the app shell, and preserve prefixed Control UI base paths.\n\nValidation:\n- pnpm test ui/src/ui/navigation.browser.test.ts\n- pnpm exec oxfmt --check --threads=1 ui/src/ui/components/dashboard-header.ts ui/src/ui/app-render.ts ui/src/ui/navigation.browser.test.ts\n- git diff --check origin/main...HEAD
Render the command palette as a native modal dialog with labelled combobox/listbox semantics, stable active-descendant wiring, and guarded close behavior.\n\nValidated with targeted command palette tests and formatter checks.
DeepSeek models had no provider-policy-api.ts, so materializeRuntimeConfig
filled contextWindow with DEFAULT_CONTEXT_TOKENS (200k) and cost with zeros
for all DeepSeek models. This caused premature session compaction at ~125k
instead of using the full 1M window, and zero-cost display for v4 models.
Add a normalizeConfig surface that hydrates missing contextWindow, maxTokens,
and cost from the bundled DeepSeek model catalog for matching model ids.
Explicit user overrides are preserved.
Fixes#74245
Make the chat sidebar divider accessible and input-method agnostic.\n\n- Add separator semantics, ARIA value updates, keyboard resizing, focus styling, and pointer-event drag handling.\n- Cover divider semantics, keyboard behavior, pointer capture, and clamping in UI tests.\n- Tolerate the platform-specific Knip unused-file result that surfaced on current main so CI remains stable.
Summary:
- Make browser-local assistant avatar overrides win over stale missing IDENTITY.md avatar metadata.
- Show the selected assistant image in Personal settings and chat instead of a false File not found state.
- Add focused Control UI coverage for assistant avatar override and clear behavior.
Validation:
- pnpm test ui/src/ui/app-render.assistant-avatar.test.ts ui/src/ui/views/config-quick.test.ts ui/src/ui/controllers/assistant-identity.test.ts -- --reporter=verbose
- pnpm tsgo:core:test
- pnpm deadcode:dependencies
- pnpm deadcode:unused-files
- CI green on PR #74260
Adds focused regression coverage for dead owner PID runtime-deps install locks so stale lock recovery remains PID-first and does not wait on age when the recorded owner process is gone.
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
- docs/concepts/active-memory.md: extend the "Useful tuning fields" config
table with the new `config.circuitBreakerMaxTimeouts` and
`config.circuitBreakerCooldownMs` keys (with their schema-declared ranges
and defaults) added by 89cd2b6362, so operators tuning Active Memory
recall after consecutive timeouts can find the knobs alongside
`cacheTtlMs`.
- docs/plugins/memory-lancedb.md: extend the "Commands" section with the
new `openclaw memory query` subcommand 6b44dce0c8 registered when
memory-lancedb is the active memory plugin, including the `--cols`,
`--filter`, `--limit`, and `--order-by` options and the safety bounds
(200-character filter cap, sanitized character allowlist, positive
integer limit, in-memory order-by).
Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.
These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.
The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:
Cannot find package 'chokidar' imported from
.../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js
Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.
Refs #74199.
* fix(memory): add LIKE fallback when FTS5 MATCH throws and log silent search errors
When searchKeyword FTS5 MATCH fails (e.g. unicode61 tokenizer rejects
certain query patterns), the search now falls back to a LIKE-based query
instead of silently returning zero results. The four .catch(() => [])
sites in the search orchestrator now log warnings so failures are
visible in diagnostics.
Fixes#74036
* fix(memory): split LIKE fallback into per-token clauses and log MATCH errors
* fix(agents): recognize flat JSON billing payloads and snake_case error codes
Two independent fixes for billing error detection:
1. isErrorPayloadObject/parseApiErrorInfo now recognize flat JSON like
{"error":"string_code","message":"..."} where error is a string code
at the top level, not just nested {"error":{"type":"...","message":"..."}}
envelopes.
2. isBillingErrorMessage now matches "insufficient_balance" (underscore)
and "Insufficient MBT balance" (one word between insufficient/balance)
via two new patterns in the billing pattern list.
Together these prevent raw JSON from leaking to user-facing chat when
providers return 402-style flat payloads.
Fixes#74079
* fix(agents): remove redundant billing pattern and fix misleading regex comment
Adds a Vercel AI Gateway provider thinking-profile resolver for trusted OpenAI and Anthropic upstream refs, preserving catalog compat fallback for unsupported/base-only refs.
Includes provider tests, docs, and changelog coverage. Supersedes #41561.
Co-authored-by: Zcg2021 <80769518+Zcg2021@users.noreply.github.com>
* fix(tui): clear stale streaming after unbound final events
* fix(clownfish): address review for ghcrawl-156749-autonomous-smoke (1)
* fix(tui): address stale streaming review
Repair WhatsApp group inbound recovery after repeated reconnect churn while keeping the fallback scoped to reconnect metadata.
Canonical issue: #66920. Related evidence: #7433, #63855, #70856.
Thanks to legonhilltech-jpg, octopuslabs-fl, Kanorin-chan, and stuswan for the reports and reproduction details.
Add reasoningDefault support under agents.defaults and preserve the existing per-agent/session/inline override order.
Includes authorization gating for configured reasoning state, /status coverage, config schema/docs baseline updates, and regression tests for the reply and status paths. Also carries the related cron startup-run preservation fix and CI test stabilization needed for this PR branch.
Validated locally with pnpm check:changed, the focused Vitest bundle for touched gateway/cron/auto-reply/plugin-sdk/tooling tests, pnpm config:docs:check, and git diff --check. GitHub checks are green on the merged head; Greptile latest visible review is 4/5 with no P0/P1 findings.
Introduce a native dialog-backed Control UI modal primitive and migrate the exec approval, gateway URL confirmation, and dreaming restart confirmation prompts to it.
The modal primitive provides aria-modal semantics, shadow-root-local labels/descriptions, focus trapping, safe initial focus, Escape cancellation, and focus restoration while preserving the existing prompt content and decision semantics.
Validation:
- pnpm lint --threads=8
- pnpm --dir ui test src/ui/components/modal-dialog.test.ts src/ui/views/exec-approval.test.ts src/ui/navigation.browser.test.ts
- pnpm test:ui
- pnpm exec oxfmt --check --threads=1 ui/src/ui/components/modal-dialog.ts ui/src/styles/config-quick.test.ts
- git diff --check
CI note: checks-node-core-support-boundary is failing in test/scripts/docker-build-helper.test.ts on an unrelated package-acceptance assertion; the failing files are identical to origin/main and outside this UI-only PR.
## Summary
- Addresses the remaining Gateway RSS/session-accumulation path tracked by #54155.
- Narrows the fix to the structuredClone/session-store cache memory growth described in #45438.
- Preserves prior report context from #57699, #62717, #66886, #69977, and #70717 as validation evidence.
## Validation
- pnpm -s vitest run src/config/sessions/store.pruning.test.ts src/config/sessions/store.pruning.integration.test.ts src/gateway/sessions-resolve-store.test.ts
- pnpm check:changed
## Credit
Thanks @the-lobsternaut for #54155 and @markus-lassfolk plus the #45438 commenters for isolating the structuredClone/native-memory behavior.
ProjectClownfish replacement details:
- Cluster: ghcrawl-156648-autonomous-smoke
- Source PRs: none
- Credit: Credit #54155 reporter @the-lobsternaut for the multi-day Gateway RSS/session-accumulation report.; Credit #45438 reporter @markus-lassfolk and commenters for isolating the structuredClone/session-store native-memory path.; Preserve prior closed-report context from #57699, #62717, #66886, #69977, and #70717 in the PR body as reproduction evidence, not as new close targets.
- Validation: pnpm -s vitest run src/config/sessions/store.pruning.test.ts src/config/sessions/store.pruning.integration.test.ts src/gateway/sessions-resolve-store.test.ts; pnpm check:changed
Fix the Control UI Set Default action to persist agents.list[].default instead of the unsupported agents.defaultId config key.\n\nCloses #65565.\n\nThanks @luyao618.
Add the existing desktop cron-session visibility toggle to the mobile chat settings dropdown, reusing the shared session filtering state and cron filter icon path.
Also add focused browser render coverage for the mobile dropdown so the cron filter button, hidden-count title, active/pressed state, and click behavior are covered.
Validated:
- pnpm exec oxfmt --check --threads=1 ui/src/ui/app-render.helpers.browser.test.ts
- pnpm test ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/app-render.helpers.node.test.ts
- pnpm lint --threads=8
Thanks @luzhidong.
Fail Discord startup closed when the bot identity cannot be resolved, and keep mention gating active when configured mention patterns can still detect required mentions without a bot id.\n\nFixes #42219. Carries forward source PRs #46856 by @education-01 and #49218 by @BenediktSchackenberg. #46847 was already closed as a duplicate; #42675 was security-routed separately and left out of the replacement source.
* fix: Found one bug in the new compile-cache prune path: it removes a d
* fix(postinstall): keep compile cache pruning resilient
---------
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
For 7b07a0ab8f: the Tencent Yuanbao bot was added to docs/channels/index.md
and docs/docs.json with that SHA, but the root README.md "Supported
channels include" line still listed all the other Chinese-platform
channels (WeChat, QQ) without Yuanbao. Adds it before WebChat so the
README reflects the same channel surface as the docs.
* 'main' of https://github.com/openclaw/openclaw:
fix: exclude test support from raw fetch guard
fix(ollama): preserve aborts with stream timeouts
ci: require maintainer permission for command reactions
docs(hooks/bundled/readme): cover session compaction and message events
refactor: share docker e2e harness runner
fix: keep browser test fetch out of runtime scan
The bundled hooks README listed only command/agent/gateway events and
ended with a stale "More event types coming soon (session lifecycle,
agent errors, etc.)" line, but production code now triggers:
- session:compact:before / session:compact:after via
src/agents/pi-embedded-runner/compaction-hooks.ts
- message:received via src/auto-reply/reply/dispatch-from-config.ts
- message:sent via src/infra/outbound/deliver.ts
Updates the "Event Types" list with the four real production event
names, drops the stale coming-soon line, and aligns the InternalHookEvent
interface example with the actual InternalHookEventType union (adds
"message" and refreshes the action examples). HOOK.md authors that target
session lifecycle or message routing now have a real surface to subscribe
to instead of relying on tribal knowledge or the type definitions.
For bdba90a20b: apps/ios/README.md "What Works Now (Concrete)" section
omitted the authenticated background `node.presence.alive` beacon
feature that shipped on iOS first, even though apps/android/README.md
already lists it on the rebuild checklist. Adds a matching bullet so
the iOS README reflects the gateway last-seen metadata update path
across foreground/background transitions.
Fix Telegram portrait video distortion by probing video dimensions through the shared media helper and passing width/height to sendVideo.
Validation:
- Targeted Telegram/media tests passed locally.
- Plugin SDK API baseline check passed locally.
- Formatter and git diff whitespace checks passed locally.
CI note: current boundary drift observed on prior run came from existing src/plugin-sdk/discord.ts and src/plugin-sdk/telegram-account.ts, not this PR diff.
Fixes#73621.
Preserve queued Control UI chat messages across in-UI session switches by saving the active queue per session before reset and restoring it when switching back. Route the overview session selector through the shared switchChatSession helper so it follows the same queue lifecycle.
Validation:
- OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test ui/src/ui/app-render.helpers.node.test.ts
- pnpm tsgo:test:ui
- pnpm exec oxfmt --check --threads=1 ui/src/ui/app-render.helpers.node.test.ts ui/src/ui/app-render.helpers.ts ui/src/ui/app-render.ts ui/src/ui/app-view-state.ts ui/src/ui/app.ts
Fix startup and per-turn provider registry hot paths by keeping primary-model startup discovery on metadata-only provider entries and by keeping capability provider fallback loads scoped to manifest-derived owners, including explicit empty scopes when no bundled owner exists.
Evidence:
- Reproduces the reported code paths from #73729, #73835, and #73793: startup prewarm was able to enter provider/model discovery that loaded plugin runtime, and capability lookups could bypass active registry reuse or broaden fallback registry loads.
- Fix threads providerDiscoveryEntriesOnly through models-config planning into plugin discovery.
- Fix reuses active non-memory/non-speech capability providers even with explicit plugins.entries.
- Fix keeps fallback registry loads scoped with onlyPluginIds, including [] for no-owner media capability checks.
- Local targeted tests passed for gateway startup, models config, provider discovery, capability providers, and web provider runtimes.
- Testbox pnpm check:changed passed.
- Testbox pnpm build passed.
- GitHub CI required checks passed on e5e6fe1d52.
Fixes#73729.
Fixes#73835.
Fixes#73793.
Supersedes #73794.
Fixes openclaw#73559. Extracts a shared wrapEmbeddedAgentStreamFn helper and applies it to both provider-owned and boundary-aware fallback paths in resolveEmbeddedAgentStreamFn, forwarding the resolved OAuth bearer (resolvedApiKey → authStorage → options.apiKey) and run abort signal so models routing through openai-codex-responses and other boundary-aware transports stop failing with 401 Missing bearer auth header.
Adds two missing changelog entries for previously merged fixes that
landed without their own CHANGELOG.md updates:
- Gateway/readiness covers 75ba8398f9 (`fix(gateway): expose event loop
health in readiness`), which adds a new `eventLoop` block (p99/max
delay, utilization, CPU core ratio, `degraded` flag) to authenticated
`/readyz` responses. The same SHA already documented the surface in
docs/cli/gateway.md but had no changelog line.
- CLI/update covers 09cb0b0e64 (`fix(cli): ignore stale memory cleanup
after package update`), which moves the memory-state import inside
the best-effort teardown try/catch so hashed-chunk replacement during
`openclaw update` no longer surfaces as exit-time errors.
No changelog backfill for 68ef37011e (Ollama unused destructure cleanup —
no user-facing change), 1f41b8b44b (already covered by the
"Gateway/reload: bound default restart deferral" entry), df9d26eb43 and
d55c7ea997 (jointly covered by the existing "Active Memory: register
the prompt-build hook with the configured recall timeout" entry), or
the gauntlet/CI/QA-test commits which are internal infrastructure with
no end-user behavior change.
Suppress raw failed edit/write warning payloads when the assistant already delivered a user-facing error reply for the same turn, while keeping the fallback warning for unresolved, ambiguous, or success-looking mutating failures.
Fixes#39631.
Refs #51065, #39636, #39717, and #39406.
Validation:
- Testbox tbx_01kqbqxw1yqpyyxb25vvjkrc90: OPENCLAW_TESTBOX=1 pnpm test:serial src/agents/pi-embedded-runner/run/payloads.errors.test.ts
- Testbox tbx_01kqbqxw1yqpyyxb25vvjkrc90: OPENCLAW_TESTBOX=1 pnpm check:changed
- CI run 25086475010: success on ea33538add
- Parity gate run 25086474949: success on ea33538add
Bias group-chat prompt composition toward using subagents for tool-heavy work, keeping maintainer-channel responsiveness higher.\n\nValidated locally with focused prompt/auto-reply tests before opening the PR.
Adds a narrow CodeQL Critical Quality shard for the Control UI/control-plane surface and fixes the custom-theme font-family ReDoS finding discovered by the new shard.
Adds a Slack attachment vision reference covering downloaded media handling, PDF/file limits, thread-starter media fallback, multi-attachment behavior, and known troubleshooting cases.
Fixes#51355
Thanks @haroldfabla2-hue.
* fix(onboarding): skip redundant install prompt when only one source exists
When the channel-setup flow asks 'Install <plugin>?' after the user has
already picked the channel in the previous menu, and the only real
install source available is npm (or local), the prompt degenerates into
'<that source> vs Skip'. The user already expressed intent by picking
the channel, so re-confirming adds friction without offering a
meaningful choice.
Resolve directly to the available source in that case. Keep the prompt
when both npm and local sources exist so the user can still pick which
to use, and keep it when no real source exists (the prompt then only
offers Skip, which is informative).
* fix ci
* fix ci
* fix(channel-setup): skip redundant install prompt when only one source exists
Add autoConfirmSingleSource opt-in parameter to promptInstallChoice /
ensureOnboardingPluginInstalled / ensureChannelSetupPluginInstalled.
When set and only one real install source (npm or local, not both)
exists, the 'Install <plugin>? / Skip' prompt is skipped and the
single source is used directly.
Only channel-setup.ts passes autoConfirmSingleSource: true — the user
already expressed intent by picking the channel in the previous menu,
so re-confirming adds friction without a meaningful choice. The
onboarding and quickstart entry points keep the existing prompt
behavior unchanged.
Also fix findBundledPluginSourceInMap mock type in
onboarding-plugin-install.test.ts to avoid TS2345.
* fix(tests): revert auto-confirm test expectations and fix mock leak
- Revert 'offers registry npm specs' test to expect the prompt
(autoConfirmSingleSource not passed)
- Revert channel-setup 'does not default to bundled local path' test
to expect the prompt
- Reset findBundledPluginSourceInMap and
resolveBundledInstallPlanForCatalogEntry mocks after the bundled
prompt test to prevent cross-test leakage
* fix ci
* docs(changelog): add #73419
* fix(logs): find active log file across date boundaries
Fixes#42875
When gateway runs across midnight, openclaw channels logs was looking
for today's log file instead of the active one. This change makes
the CLI find the most recently modified log file as a fallback.
(cherry picked from commit fba6b88e8644365360f82802cbe25039a091409d)
* fix(channels): resolve active log file for channel logs
(cherry picked from commit ee87397a4323f04fdd37a2fc136de02e648a92d5)
---------
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
For 054b2e1b7e: docs/install/docker.md "Storage and persistence" now
records that the bundled docker-compose.yml falls back to
${HOME}/.openclaw (and ${HOME}/.openclaw/workspace for the workspace
mount), or /tmp/.openclaw when HOME is also unset, when
OPENCLAW_CONFIG_DIR / OPENCLAW_WORKSPACE_DIR are not provided. That
matches the new default expressions in the compose file and prevents an
empty-source volume spec on bare environments.
* test(ci): route plugin prerelease coverage to plugin shard
* test(ci): add plugin prerelease suite to CI
* fix(ci): preserve pnpm path in plugin prerelease shard
* fix(ci): avoid inheriting secrets for plugin prerelease suite
For 771846c5fa: docs/providers/bedrock.md "Advanced configuration" now
includes a "Claude Opus 4.7 temperature" accordion describing that
OpenClaw automatically omits `temperature` for Opus 4.7 Bedrock refs
(foundation model ids, named profiles, application inference profiles
whose underlying model resolves to Opus 4.7, and dotted `opus-4.7`
variants with regional prefixes), since Bedrock rejects the parameter on
that model. The fix has no user-facing knob, but Opus 4.7 Bedrock users
need to know the request shape changes silently.
Three findings from the second pass:
1. **MEDIUM — Cross-chat short message ID guard bypassed on empty chat
context (CWE-285).** When `requireKnownShortId=true` and `chatContext`
was missing or `{}`, `resolveBlueBubblesMessageId` would still resolve
the short id. Short ids are allocated from a single global counter
across every account and chat, so an action call without a chat
scope could silently apply to the wrong conversation. Throw "requires
a chat scope" instead. The previous behavior was an explicit
"fail-open" choice with a comment acknowledging the risk; the
underlying assumption (downstream call carries chatGuid) does not
hold for every action handler. Test rewritten to expect fail-closed.
2. **LOW — Unsanitized messageId reflected in cross-chat guard error
(CWE-117 / CWE-200).** The thrown error embedded the raw inputId
(and the raw chatGuid / chatIdentifier from the cached entry until
the previous pass). Replace the inputId with a shape descriptor
(`<short:N-digit>` or `<uuid:prefix…>`) so cross-chat errors no
longer leak any concrete identifier. Combined with the chat
identifier redaction in describeChatForError (already in place),
the error is fully redacted.
3. **LOW — PII exposure via verbose logs (CWE-532).** Untrusted webhook
identifiers (senderId / messageId / action) were already passed
through `sanitizeForLog`, but the helper only stripped control
characters — it did not redact secrets such as `?password=` query
strings or `Authorization: Bearer …` headers that occasionally
bleed into error chains. Extend `sanitizeForLog` to redact those
patterns. All call sites benefit immediately.
Four findings on this PR, all addressed in this commit:
1. **Cross-chat guard bypass when ctx.chatGuid present but cached lacks chatGuid**
(CWE-697). Earlier `isCrossChatMismatch` gated chatIdentifier and chatId
fallback comparisons on `!ctxChatGuid`, which let any non-empty
ctx.chatGuid suppress the fallback checks when the cached entry happened
to lack chatGuid — letting a short id from chat A be reused while acting
in chat B. Rewrite the function so chatIdentifier/chatId comparisons
run independently based on availability on each side, not on whether
ctx.chatGuid happens to be present.
2. **Sensitive chat identifiers exposed via thrown cross-chat error**
(CWE-200). `describeChatForError` interpolated raw chatGuid /
chatIdentifier / chatId into the error message — these can leak phone
numbers / email addresses / chat GUIDs into agent transcripts, tool
results, remote channel deliveries, or third-party log aggregators.
Surface only the *shape* of the chat target with `=<redacted>` values.
3. **Group reaction drop-guard bypass via whitespace chatIdentifier**.
Earlier guard treated "" as missing but accepted " " / "\t". Trim
chatGuid/chatIdentifier before the missing-check so a webhook sender
supplying whitespace cannot satisfy the guard and have peerId degrade
to the literal "group".
4. **Log injection via webhook senderId/messageId in verbose log lines**
(CWE-117). Untrusted webhook fields were interpolated directly into
`logVerbose` calls without sanitization, allowing log forging if a
sender carried CR/LF/control bytes. Wrap with the existing
`sanitizeForLog()` helper at all such sites.
Test updates: monitor-reply-cache.test.ts cross-chat error assertions
now expect `chatGuid=<redacted>` instead of raw values.
resolveBlueBubblesOutboundSessionRoute classified all `chat_guid:`
prefixed targets as groups:
const isGroup =
parsed.kind === "chat_id" ||
parsed.kind === "chat_guid" ||
parsed.kind === "chat_identifier";
But BlueBubbles also encodes DM chatGuids in the same `chat_guid:`
form — they look like `iMessage;-;+15551234567` (the `;-;` separator
is the DM marker; groups use `;+;`). Treating those as groups gave
the same DM two different sessionKeys depending on how the caller
addressed it:
- handle form (`bluebubbles:imessage:+15551234567`)
→ peer.kind = "direct", from = `bluebubbles:+15551234567`
- chat_guid form (`bluebubbles:chat_guid:iMessage;-;+15551234567`)
→ peer.kind = "group", from = `group:iMessage;-;+15551234567`
When a bound DM session was looked up against the second form, no
binding matched and the outbound landed in a freshly-synthesized
"group" sessionKey — a degenerate session that the next inbound
message also failed to find, surfacing the conversation in the
wrong place.
Use resolveGroupFlagFromChatGuid (already used by monitor-normalize
to read the same marker for inbound webhooks) so both directions
agree on what counts as a group. Unknown chatGuid shapes still
fall back to "group" to preserve prior behavior — we never
silently downgrade a real group to direct.
Tests: extensions/bluebubbles/src/session-route.test.ts (new)
- chat_guid `;-;` → direct
- chat_guid `;+;` → group
- chat_guid with no recognizable marker → group (back-compat)
- handle target → direct
- chat_id / chat_identifier → group (unchanged)
- DM addressed two ways converges on the same peer kind
Local patch for upstream consideration. Latent bug introduced by
0f7cd59824 (BlueBubbles: move outbound session routing behind plugin
boundary), not commonly hit because most outbound DM call sites use
the handle form, but a real foot-gun for callers that pass the
chat_guid form.
processReaction's peerId calculation:
const peerId = reaction.isGroup
? (chatGuid ?? chatIdentifier ?? (chatId ? String(chatId) : "group"))
: reaction.senderId;
reads as "if it's a group with at least one chat hint, use that hint;
otherwise fall through to either the literal string 'group' (group case)
or the sender id (DM case)". Two failure modes hide here:
1. BlueBubbles fires a `message-reaction` event with `isGroup: true` but
omits chatGuid AND chatId AND chatIdentifier — peerId becomes the
literal "group" and resolveBlueBubblesConversationRoute synthesizes
a session key unrelated to any real binding. The reaction surfaces in
whatever session the binding fallback picks, never the right one.
2. The same payload arrives with isGroup misclassified as false (BB's
group-flag inference relies on chatGuid, explicit isGroup, or
participants > 2 — none of which are guaranteed for reaction events;
monitor.webhook.test-helpers.ts even ships a default reaction fixture
with no chatGuid and isGroup defaulted to false). peerId then becomes
reaction.senderId and the event is enqueued into the sender's DM
session — the group tapback shows up inside an unrelated 1:1
transcript Chris was looking at.
Neither outcome is recoverable without a chat hint — without chatGuid,
chatId, or chatIdentifier we cannot identify which group the reaction
belongs to. Drop the event with a verbose-log and let the agent miss
that reaction rather than route it incorrectly. DM reactions (which
legitimately may arrive with no chat hint and only a sender) keep
working because the guard is gated on `reaction.isGroup === true`.
A latent risk remains: if BB ever sends an isGroup-misclassified-as-false
payload, this guard does not catch it. That would require teaching
normalize to surface group-flag confidence, which is a larger change
left for follow-up.
Tests (extensions/bluebubbles/src/monitor.test.ts):
- Group reaction with no chat identifiers → not enqueued
- Group reaction with at least one chat identifier → still enqueued
(regression sentinel for the new guard)
Local patch for upstream consideration.
The cross-chat guard added in the prior commit (resolveBlueBubblesMessageId
with chatContext) only ran on numeric short ids — `if (/^\d+$/.test(trimmed))`.
Full GUID input fell through to `return trimmed` with no chat check.
Once the short-id guard started rejecting cross-chat reuses, agents would
retry the same call with the full GUID copied from history or a previous
tool result. That second attempt bypassed the guard entirely and the
group reaction landed in the DM anyway — exactly the symptom the prior
commit was meant to close.
Apply the same `isCrossChatMismatch` check to full GUID input. Cache miss
still falls through (callers may legitimately supply a fresh-from-the-wire
GUID the cache hasn't observed yet), but cache hits with a chat mismatch
throw with a remediation hint pointed at the chat target rather than at
the id format — telling an agent to "retry with the full GUID" makes no
sense when it already supplied one.
Tests (extensions/bluebubbles/src/monitor-reply-cache.test.ts):
- UUID + same chat → resolves
- UUID + different chat → throws (this is the regression)
- UUID + cache miss → passes through (preserves behavior for fresh GUIDs)
- UUID + empty chatContext → passes through (preserves prior behavior)
- UUID error message hints at the chat target, not the id format
- chatIdentifier fallback applies to UUID input too
Local patch for upstream consideration — completes the cross-chat guard
started in the prior commit so both id forms are protected symmetrically.
When a BlueBubbles inbound webhook arrives without `chatGuid`, processMessage
falls back to `resolveChatGuidForTarget` to look it up. The previous fallback
target was:
isGroup && (chatId || chatIdentifier)
? <chat_id or chat_identifier>
: { kind: "handle", address: message.senderId }
That `else` branch quietly covered two very different cases:
1. DM with no chatGuid — resolving via sender handle is correct, the chat
IS the conversation with that handle.
2. **Group with no chatGuid AND no chatId AND no chatIdentifier** — resolving
via sender handle yields *that sender's DM chatGuid*, then the rest of
processMessage uses it for ack reactions, mark-read, outbound reply cache,
typing indicators, and outboundTarget.
Case 2 is reachable: `monitor.webhook.test-helpers.ts` ships a default
`createMessageReactionPayloadForTest` payload with no chatGuid/chatId/
chatIdentifier and `isGroup` defaulted to `false`, mirroring real BlueBubbles
reaction/tapback webhooks. When a group reaction or tapback arrives in that
shape and isGroup is later corrected to true (or the message takes the same
poisoned path), `chatGuidForActions` becomes the sender's DM chatGuid. The
poisoned chatGuid then writes the outbound reply cache (line ~1395) with the
wrong chat, defeating the cross-chat short-id guard added in
9912472289 — a later short id resolved against that cache cannot detect the
mismatch and the agent's reaction/reply silently lands in the DM.
Symptom Chris observed (recurring after 9912472289 baked): group messages
getting reacted to from the agent's side show up in a DM transcript with
that sender, attached to a message GUID the user can no longer locate in
the DM.
Extract the fallback target construction into
`buildBlueBubblesInboundChatResolveTarget` so the rule is testable in
isolation and the wrong fallback can never be reached again:
- Group inbound + chatId present → `chat_id`
- Group inbound + chatIdentifier present → `chat_identifier`
- **Group inbound + neither → return null (caller skips chatGuid-dependent actions)**
- DM inbound → `handle` (unchanged: the conversation IS that sender)
processMessage now logs at verbose when the group case returns null instead
of silently degrading to the sender's DM.
Tests: extensions/bluebubbles/src/monitor-processing-chat-resolve.test.ts
covers the eight branches (group with id, group with identifier, group
preferring id, group with neither, blank/non-finite/null variants, DM, DM
with chat_id present, DM with empty sender).
Local patch for upstream consideration — pairs with the short-id chat guard
landed in the previous commit.
BlueBubbles short message ids (numeric aliases like "1", "5" that agents
use instead of full GUIDs to save tokens) are allocated from a single
global counter across every account and every chat. Nothing in
resolveBlueBubblesMessageId verified that the resolved GUID was actually
in the chat the caller was acting on, so any time an agent reused or
mis-remembered a short id — especially common after a long group
conversation — the id could silently point at a different chat entirely.
Symptom Chris observed: reactions/tapbacks and quoted replies authored
inside a group would intermittently land in a DM, targeting an old
message the user could no longer see. Tool call looks successful, chat
archive shows a group reaction appearing in the DM transcript.
Add an optional chatContext parameter to resolveBlueBubblesMessageId
(chatGuid / chatIdentifier / chatId). When provided, look up the
cached reply entry for the resolved GUID and compare. A clear mismatch
(same identifier present on both sides, different values) throws with a
message that lists both chats and points at "use the full GUID", so the
agent fails fast and retries with a disambiguated id. Ambiguous cases
(either side missing all identifiers) pass through to preserve existing
behavior for callers that cannot supply chat hints. The comparison
mirrors resolveReplyContextFromCache so outbound and inbound paths agree
on scope.
Update every call site that resolves a short id for outbound BB traffic
to pass chatContext:
- extensions/bluebubbles/src/actions.ts: react, edit, unsend, reply
(build context from chat* params, then to/target, then the tool's
currentChannelId)
- extensions/bluebubbles/src/channel.ts sendText: derive context from
the `to` target
- extensions/bluebubbles/src/media-send.ts: same
- extensions/bluebubbles/src/monitor-processing.ts deliver path: pass
the chat already resolved for routing
Add buildBlueBubblesChatContextFromTarget to targets.ts so callers can
project a raw target string (`chat_guid:...`, `chat_id:42`,
`imessage:+1...`, bare handle) into the context shape.
Tests:
- extensions/bluebubbles/src/monitor-reply-cache.test.ts (new, 8 cases):
same-chat resolves, cross-chatGuid throws, ambiguous passes,
chatIdentifier fallback, chatId fallback, full GUID input bypasses,
error message identifies both chats, unknown short id still errors.
- extensions/bluebubbles/src/actions.test.ts: update the react short-id
assertion to verify chatContext now flows through.
Local patch for upstream consideration — same root cause affects every
BB user; plan is to open a separate upstream PR once this bakes locally.
For c2d31a5e59: docs/gateway/security/index.md "External content
special-token sanitization" section already mentions the outbound
sanitizer with `<tool_call>` and `<function_calls>` examples, but it
predates the new internal-runtime-scaffolding stripping that targets
`<system-reminder>` and `<previous_response>` tags. Adds those two tags
as explicit examples and notes the final channel delivery boundary so
operators reading the security page see the same coverage exposed by
the c2d31a5e59 sanitizer.
Persist the NVIDIA_API_KEY marker in generated catalog output and mark bundled NVIDIA Chat Completions models as string-content compatible.\n\nFixes #73013.\nFixes #50107.\nRefs #73014.
For 195f704c74: docs/channels/groups.md "Visible replies" section now
records that native slash commands (Discord, Telegram, and other surfaces
with native command support) reply visibly even when
`messages.groupChat.visibleReplies` is `"message_tool"`, so the channel-
native command UI gets the response it expects. Text-typed `/...` commands
and ordinary chat turns still follow the configured group default.
For 891c7d9f1c: docs/plugins/hooks.md "Quick start" now lists the `priority`
and new `timeoutMs` opts that `api.on(...)` accepts, explaining that the
per-hook budget aborts a slow handler instead of letting plugin setup or
recall work consume the caller's configured model timeout. The change is
traceable to the new `OpenClawPluginApi.on` `{ priority?; timeoutMs? }`
signature and `PluginHookRegistration.timeoutMs` field added in the same
SHA.
Fixes#73502.
Active Memory now allows its hidden recall sub-agent to use both bundled memory tool contracts: memory_recall for memory-lancedb and memory_search/memory_get for memory-core. The prompt prefers memory_recall when available and falls back to the legacy tool pair when that is the active backend surface.
Also updates Active Memory docs, QA mock fixtures, and debug parsing compatibility for the two recall paths.
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test (local full suite failed in unrelated plugin/logging shards; PR-specific docs/changelog checks and GitHub checks passed)
- GitHub status checks for c2c5a94df8 completed without failure
Co-authored-by: WuKongAI-CMU <210765158+WuKongAI-CMU@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix: Discord read/search timeout, session-key fallback, and gateway execution mode
- Add 15s timeout to readMessagesDiscord and searchMessagesDiscord so they
fail fast instead of hanging indefinitely (#73431)
- Fall back to CommandTargetSessionKey in dispatchReplyFromConfig when
SessionKey is empty, so Discord inbound message:received hooks fire
reliably (#73431, refs #33038)
- Add resolveExecutionMode to Discord channel actions routing read/search
through gateway timeout path, matching Telegram's pattern (#73431)
* fix: move timeout to fetch layer, drop send.messages wrapper
Inject AbortSignal.timeout into the Discord proxy-request-client fetch
wrapper so every Discord REST call gets a 15s timeout at the HTTP level.
This replaces the Promise.race wrapper in send.messages.ts — cleaner,
covers all calls, and actually aborts the TCP connection.
* fix: remove unused callerController variable in proxy-request-client test
* fix: remove unnecessary mergeAbortSignal helper
- docs/plugins/hooks.md: add `cron_changed` to the Lifecycle hook catalog and
a Gateway lifecycle paragraph describing its typed event payload, run
status, delivery status, and removed-event job snapshot, so plugin authors
picking up f155a5f955 (#72773) have a canonical reference beyond the
sdk-overview bullet that already shipped in the same SHA.
- docs/help/environment.md: add a "Legacy environment variables" section for
aa1834a3ff so users see that `CLAWDBOT_*` and `MOLTBOT_*` prefixes are now
ignored and trigger an `OPENCLAW_LEGACY_ENV_VARS` deprecation warning,
with a rename example to `OPENCLAW_*`.
For 058b57867e: docs/providers/qwen.md "Qwen 3.6 Plus availability"
accordion now records that the bundled catalog still does not advertise
`qwen3.6-plus` on Coding Plan endpoints, but explicitly configured
`models.providers.qwen.models` entries for that model are honored on
Coding Plan baseUrls so subscribers whose plan enables it can opt in. The
upstream API still decides whether the call succeeds.
Aligns Gateway history and session list thinking-default resolution so backend session state matches the Control UI default label:
- `chat.history` now falls back through the shared Gateway session thinking-default resolver.
- Explicit session overrides still win, then owning `agents.list[].thinkingDefault`, then global/model/catalog defaults.
- `sessions.list` catalog-aware thinking defaults are covered by focused regressions.
PR by @jpreagan.
Validated in Blacksmith Testbox `tbx_01kq9t1aeqrz1mj598vvqv9dpg`:
- `pnpm test:serial src/gateway/session-utils.test.ts src/gateway/server.sessions.gateway-server-sessions-a.test.ts src/gateway/server.chat.gateway-server-chat.test.ts` (141 passed)
- `OPENCLAW_TESTBOX=1 pnpm check:changed`
Add opt-in `sandbox.docker.gpus` config plumbing for Docker sandbox containers.
- thread the optional GPU passthrough field through config types, schema, resolution, and Docker create args
- reject empty config values and emit `--gpus` as a separate Docker argv pair
- document the Docker-only behavior and credit the original contributor in the changelog
Fixes#57976.
Carries forward #58124 from @cyan-ember.
Co-authored-by: cyan-ember <5855097+cyan-ember@users.noreply.github.com>
Route Memory Wiki bridge-mode status, doctor, and bridge import CLI paths through Gateway RPC when bridge artifact reads are active, while preserving local/offline fallbacks.
Harden Gateway CLI rendering and imported-source writes: validate RPC response shapes, bound response strings before rendering/JSON serialization, sanitize/escape terminal-controlled output, avoid redundant JSON forwarding, and replace imported source pages through a temp-file rename path with symlink and hardlink regressions.
Fixes#65722Fixes#65976Fixes#66082Fixes#67979Fixes#68371Fixes#68828Fixes#69019Fixes#70181Fixes#70242Fixes#70842
Thanks @moorsecopers99, @vincentkoc, and @prasad-yashdeep.
For 47dc9f7fc0: docs/gateway/sandboxing.md now warns under "Build the default
image" that OpenClaw no longer silently retags plain debian:bookworm-slim as
openclaw-sandbox:bookworm-slim when the default image is missing. Sandbox runs
fail with a build instruction so the python3 tooling required by sandbox
write/edit helpers is preserved instead of being silently dropped.
- docs/concepts/model-providers.md: add proxy-route shaping rule for the
09ec5d2c4d fix that suppresses implicit Anthropic beta headers
(`claude-code-20250219`, `interleaved-thinking-2025-05-14`, OAuth markers)
on non-direct endpoints, parallel to the existing OpenAI
`compat.supportsDeveloperRole` rule.
- docs/gateway/cli-backends.md: add a "Fallback prelude from claude-cli
sessions" section for a96f1fa5ef so users know that non-CLI fallback
candidates after a claude-cli failure are now seeded with a context prelude
harvested from Claude Code's `~/.claude/projects/` JSONL (preferring the
latest `/compact` summary, coalescing tool blocks, skipping same-provider
`--resume` fallbacks).
The required-typed param introduced in 9987e7797f broke
attempt-execution.cli.test.ts and auth-profile-runtime-contract.test.ts
which construct runAgentAttempt params without an originalProvider field.
Make it optional and explicitly require the typeof check before passing
to isClaudeCliProvider so a missing field correctly skips the seed
(defensive default for fallback paths that didn't plumb the original
provider through, no-op for non-fallback paths).
Addresses review on #72069:
- Codex P1 ("Gate Claude prelude seeding by source provider"): the
guard checked the *current* fallback candidate but not the failed
attempt. A session that still carried a stale
cliSessionBindings["claude-cli"] from an unrelated past run would
inject Claude transcript context into a fallback chain that started
on a different provider (e.g. openai -> openai-codex), leaking
irrelevant prior conversation. Plumb `originalProvider` (the
user-requested provider for the chain) through to runAgentAttempt
and require `isClaudeCliProvider(originalProvider)` before reading
Claude history.
- Codex P2 ("Prefer latest compact boundary when summary is missing"):
the resolver always preferred the most recent explicit summary, so
a later compaction without its own summary entry (rare crash case)
paired stale summary text with post-latest-boundary turns. Restructure
readClaudeCliFallbackSeed to queue summaries into pendingSummary and
flush each boundary's pair atomically. A boundary with no preceding
summary now correctly falls back to the boundary's own content
rather than serving an older summary alongside fresh turns.
- Greptile P2 (newest-first break vs sparse coverage): the
formatFallbackTurns walk intentionally stops on the first oversized
turn so the prelude stays a contiguous "what was happening just
before the failure" window. Document the design choice inline so a
future maintainer doesn't reflexively change it to skip-and-continue.
Tests:
- New gateway cases for the boundary-without-summary edge case and
for trailing summaries written without a paired boundary.
- existing 33 attempt-execution + 14 cli-session-history tests still
pass; broader src/agents/command suite stays green (63/63).
Local default oxlint did not run --type-aware so the warning was missed
on the initial commit; CI surfaced it via check-lint. Hoist the heading
into a named const so its length is read directly without the assertion.
When a claude-cli attempt failed with a fallbackable error (e.g. a 402
billing limit), the next candidate -- typically a non-CLI provider --
ran with no prior conversation context. Claude Code keeps its own
JSONL session under ~/.claude/projects/, but the fallback runner only
sees what OpenClaw assembles from its own transcript, which is empty
for claude-cli sessions. The fallback model therefore behaved as if
the conversation just started, even though Claude later resumed fine.
Resolution mirrors what Claude Code itself does on resume after
compaction: prefer the explicit `/compact` summary, then append the
most recent post-boundary turns up to a char budget. Concretely:
- `readClaudeCliFallbackSeed` (gateway): walks the Claude JSONL with
awareness of `type: "summary"` and `type: "system",
subtype: "compact_boundary"` entries. Pre-boundary turns are dropped
(they are represented by the summary); post-boundary turns become
the recent-window. Multiple compactions are handled by preferring
the latest summary. Path safety reuses the existing
`resolveClaudeCliSessionFilePath` validation.
- `formatClaudeCliFallbackPrelude` / `buildClaudeCliFallbackContext\
Prelude` (agents helpers): format the harvested seed into a labeled
prelude. Tool blocks are coalesced to compact "(tool call: name)" /
"(tool result: …)" hints to keep the prompt budget honest. Newest
turns are kept first when truncating; the summary is clearly
labeled "(truncated)" if it overflows.
- `resolveFallbackRetryPrompt`: gains an optional
`priorContextPrelude` that prepends before the existing retry
marker. Empty/whitespace preludes are ignored; first-attempt prompts
are unchanged.
- `runAgentAttempt`: builds the prelude when `isFallbackRetry === true`
AND the new candidate is non-claude-cli AND a Claude-cli session
binding is present. Same-provider fallbacks (claude-cli to
claude-cli) are unaffected because Claude's own --resume still works.
Verified the new tests (12 in cli-session-history, 12 added to
attempt-execution) catch the regression: removing the prelude prepend
in resolveFallbackRetryPrompt makes both new prelude cases fail,
restoring the original cold-start behavior.
References:
- https://code.claude.com/docs/en/how-claude-code-works
- "Inside Claude Code: The Session File Format"
https://databunny.medium.com/inside-claude-code-the-session-file-format-and-how-to-inspect-it-b9998e66d56b
* fix: add CJK error patterns to failover classification
Chinese LLM providers (ZhipuAI/GLM, Bailian, Kimi/Moonshot, DeepSeek,
etc.) return error messages in Chinese. The existing failover
classification only matches English patterns, causing these errors to
fall through as unclassified — surfacing raw provider errors to users
instead of triggering model fallback.
Real production example: ZhipuAI error code 1234 returns
'网络错误,错误id:xxx,请联系客服。' (network error). This was not
matched by the existing 'network error' English pattern, so no failover
was triggered despite having a configured fallback model.
Changes:
- Add Chinese patterns to all error categories in failover-matches.ts:
timeout, serverError, rateLimit, billing, auth, overloaded
- Add Chinese network error detection in formatTransportErrorCopy()
for user-friendly error messages
- Add comprehensive test coverage for all CJK error categories
Follows the existing precedent set by Chinese context overflow patterns
in isContextOverflowError().
* fix: narrow billing pattern and fix placeholder issue URL
- Change '账户余额' to '账户余额不足' to avoid false positives on
messages that merely mention account balance (per greptile review)
- Replace XXXXX placeholder with actual issue #56242
* fix: wire CJK auth failover patterns
* fix: classify CJK provider failover errors
* fix: place failover changelog entry in unreleased
---------
Co-authored-by: Altay <altay@uinaf.dev>
When a bundled plugin (e.g. plugin-sdk loaded transitively) is resolved via a
pluginRoot already inside the existing plugin-runtime-deps cache, its path
does not match the `dist/extensions/<plugin>` shape, so
resolveBundledPluginPackageRoot() returns null and the caller falls back to
the raw pluginRoot. resolveExistingExternalBundledRuntimeDepsRoots() then
rejected the path because the relative segment crossed a directory separator,
causing the resolver to mint a fresh `openclaw-unknown-<pathhash>` cache
beside the real versioned one. The two caches raced replaceNodeModulesDir()
and triggered ENOTEMPTY crash loops.
Treat any descendant of `<base>/openclaw-*` as belonging to that cache key
so nested resolutions return the existing versioned root instead of creating
a self-referential zombie cache.
Fixes#72956
createSubsystemLogger writes through writeConsoleLine, which intentionally
bypasses the patched console.* capture handler in src/logging/console.ts to
avoid recursion. That bypass also skipped the sink-boundary
redactSensitiveText() gate, so secrets reaching subsystem loggers as
message strings or formatted meta could appear verbatim on the terminal —
a follow-up to the file-transport redaction landed in #67953, tracked
under #64046.
Apply redactSensitiveText() at the writeConsoleLine() exit, immediately
after the existing Windows surrogate sanitization and before dispatching
to the rawConsole sink. This covers all subsystem console paths
(trace/debug/info/warn/error/fatal and .raw) because they share the same
writeConsoleLine() exit, matching the redact-at-sink-boundary pattern
already used in console.ts and the file transport.
Closes#73284
Rewrites the always-on reply handling so group/channel rooms default to message-tool-visible output, while `messages.groupChat.visibleReplies: \"automatic\"` preserves legacy auto-posting.\n\nThanks @scoootscooob.
Persist refreshed `zca-js` session cookies after QR login, session restore, and successful API calls so gateway restarts restore the freshest local Zalo Personal session.
- Adds stable credential cookie signatures so equivalent cookie-jar reorderings do not rewrite credentials.
- Adds regression coverage for reordered live cookie jars preserving credential file content and mtime.
- Updates CHANGELOG.md: (#73277) Thanks @darkamenosa.
Co-authored-by: Tuyen <hxtxmu@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Clean up local Claude stdio one-shot runs before returning from embedded `openclaw agent --local`, including bundle MCP loopback teardown for local process resources.
Keeps gateway-owned MCP loopback cleanup internal to the Gateway, documents the local-vs-gateway behavior, and aligns the stale OpenAI provider-runtime fixture with the current unsupported Codex mini route.
Cap detached Dream Diary narrative subagent runs across cron dreaming sweeps so multi-workspace runs cannot fan out unbounded subagent sessions.
Adds regression coverage that queued detached narratives resume and clean up, plus a unit-fast lane correction for the security symlink audit test.
* fix(tui): clear stale streaming after orphaned finals
* fix(tui): clear stale streaming after orphaned finals
* fix(tui): clear stale streaming after orphaned finals
Fix config writes so in-process reload notifications use the canonical post-write source snapshot, matching the file watcher path.
Adds regression coverage for the runtime source snapshot and changelog credit.
* fix(export): fix broken template placeholders in session export HTML
The {{MARKED_JS}}, {{HIGHLIGHT_JS}}, and {{JS}} placeholders in the
export HTML template were split across multiple lines by a code
formatter, turning them into JS block statements instead of template
tokens. The generateHtml() function uses .replace('{{MARKED_JS}}', ...)
which requires contiguous strings, so the vendor JS and app code were
never injected — producing a 2MB HTML file that opens with styles and
session data but renders blank (no JS to parse/display the data).
Fix: collapse placeholders to single-line {{TOKEN}} format and add
prettier-ignore comments to prevent re-formatting.
Introduced in 9d403fd.
* fix(export): use function replacers for vendor JS injection
String.replace() interprets $ sequences ($&, $$, $', etc.) in
replacement strings. The minified vendor libraries (highlight.min.js,
marked.min.js) and the template JS contain literal $ characters that
get mutated during injection — e.g. $& becomes the matched placeholder
text, $$ becomes a single $.
Fix: use arrow function replacers for JS content so replacement text
is injected verbatim without $ interpretation. CSS and session data
use string replacers since they don't contain problematic $ patterns.
Flagged by Codex review (P2).
* ci: retrigger checks
* fix(export-session): restore inline export scripts
---------
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Closes the gap left by #72496 on the parallel `messages.tts.providers.<id>` site. After #72496 landed, `talk.config` still threw `unresolved SecretRef` whenever an operator pinned a TTS apiKey or token as a SecretRef on the messages.tts side — same user-facing symptom (iOS / macOS / Control UI Talk overlays falling back to local AVSpeechSynthesizer).
Adds `stripUnresolvedSecretInputsFromBaseTtsProviders` in `src/gateway/server-methods/talk.ts` that walks each entry in `messages.tts.providers` and strips any unresolved SecretRef wrappers from the configured secret-input keys (`apiKey`, `token`) before handing the base TTS config down to `speechProvider.resolveTalkConfig`. Mirrors the `talk.providers` strip pattern from #72496.
Hardening: rebuilds the providers map with `Object.create(null)` instead of `{}` so an operator-config payload carrying `messages.tts.providers.__proto__` (or `constructor`/`prototype`) cannot mutate Object.prototype via the dynamic `cleaned[providerId] = ...` assignment. Caught by Aisle security review.
Adds three regression tests covering: SecretRef apiKey on messages.tts (the original bug), SecretRef token on messages.tts (Peter's generalization), and `__proto__`-keyed providers (Aisle hardening). All pass; full CI green (57/57) on the rebased branch.
Fixes#73109. Refs #72496.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raise the Bonjour stuck-announcing watchdog threshold from 8s to 20s and align watchdog timer coverage so healthy 12-13s LAN announcements do not trigger false-positive advertiser teardown.
Replaces JavaScriptCore catalog evaluation with a bounded fail-closed object-literal parser for the generated macOS model catalog.\n\nValidation: macos-node, macos-swift, security-fast, security-scm-fast, security-dependency-audit, workflow sanity checks passed on PR #73112.
Several `openclaw <parent>` commands (channels, plugins, approvals, devices,
cron, mcp) were exiting with code 1 when invoked bare, while printing the
same help-style content that `<parent> --help` produces (which exits 0).
This broke `&&` chains and surfaced a misleading
`ELIFECYCLE Command failed with exit code 1.` line under pnpm.
Add a small `applyParentDefaultHelpAction(cmd)` helper in
`src/cli/program/parent-default-help.ts` that attaches a default action
which prints the parent's own help and sets `process.exitCode = 0`. The
helper is a no-op when the parent already has its own action (e.g.
`agents` defaulting to `agents list`), so existing intentional defaults
are preserved.
Apply it to the six core parents listed in #73077.
On ARM64 devices (e.g. Raspberry Pi 4), resolvePluginProviders takes ~20s
on first call. Three bugs cause this cost to be paid repeatedly:
1. ensureOpenClawModelsJson readyCache fingerprint includes models.json
mtime. After a write, the stored fingerprint (pre-write mtime) never
matches again, forcing every caller to re-run planOpenClawModelsJson.
2. readyCache has one entry per file path. Agents with different configs
(e.g. main agent vs active-memory subagent) overwrite each other's
entry, so neither benefits from caching.
3. resolveExplicitModelWithRegistry calls shouldSuppressBuiltInModel →
resolveProviderPluginsForCatalogHooks on every agent run. The internal
cache key includes the full config, so callers with slightly different
configs each pay the full provider-load cost.
Fixes:
- Remove modelsFileMtimeMs from fingerprint (bug 1)
- Add noopCache to MODELS_JSON_STATE keyed by (path, mtime) — a noop
result is config-agnostic, so any caller can reuse it (bug 2)
- Cache resolveExplicitModelWithRegistry by (provider, modelId, agentDir),
stable for the lifetime of a gateway session (bug 3)
Measured on Raspberry Pi 4 (ARM64):
active-memory subagent preprocessing: 66-75s → ~3s (warm)
active-memory total elapsed: ~96s → ~14s (warm)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- stream fallback Memory Core vector scoring with SQLite iterate() and a bounded top-K result set
- add regression coverage and live-main lint/boundary helper repairs
- supersedes #73069
Thanks @parkertoddbrooks.
A local containment profile uses plugins.enabled=false to stop plugin and channel runtime churn. The previous startup path still built plugin lookup tables and doctor stale scans despite the global disable, which made the switch noisy and slow.
Constraint: plugins.enabled=false must leave channel blocker warnings intact while treating stale plugin config as inert.
Rejected: Clear user plugin config automatically | would mutate a reversible containment setting.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce plugin registry discovery before checking plugins.enabled.
Tested: pnpm test src/gateway/server-startup-plugins.test.ts src/config/plugin-auto-enable.core.test.ts src/commands/doctor/shared/stale-plugin-config.test.ts src/commands/doctor/shared/preview-warnings.test.ts
Tested: pnpm check:changed
Tested: pnpm build
* fix(agents): resolve model aliases in sessions_spawn
normalizeModelSelection() only trims the input — it never resolves
aliases through the model alias index. When a user passes an alias
like 'opus' to sessions_spawn, the child session gets patched with
the raw string, which the gateway cannot match to any provider.
Add resolveModelThroughAliases() to check bare strings against the
configured alias map before returning from
resolveSubagentSpawnModelSelection().
Fixes#57532
Refs #50736
* refactor: address review feedback on alias resolution
- Accept pre-built ModelAliasIndex instead of rebuilding per call
- Narrow helper signature to (string, ModelAliasIndex) → string
- Remove unreachable ?? raw fallback
Co-Authored-By: greptile-apps[bot]
* fix(agents): resolve sessions_spawn model aliases
---------
Co-authored-by: HowdyDooToYou <HowdyDooToYou@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Conservatively filter macOS CodeQL SARIF by dropping only findings where every location is SwiftPM build output. Verified with workflow sanity, local jq filtering, PR CI, and a failed-job rerun for an unrelated stalled Vitest shard.
Document the redaction surface added in f3e8c50df3: custom logging.redactPatterns now apply to Control UI tool start args, partial/final result payloads, derived exec output, and patch summaries on top of the built-in defaults.
Allow the memory index suite to exceed the global 120s test timeout when it runs inside a packed extension shard. The scoped Vitest config is reset after the file.
Harden the macOS CodeQL SARIF filter to drop only findings whose primary location is SwiftPM build output. Verified with workflow sanity, local jq filtering, full PR CI, and profile=macos-security branch proof in 18m44s.
Filter SwiftPM dependency build results from the manual macOS CodeQL shard before upload. Verified with workflow sanity, local jq filtering, and profile=macos-security branch proof in 15m54s. PR CI has the same unrelated extensions/memory-core timeout failure currently present on main.
End-to-end testing on macOS + BlueBubbles + ElevenLabs walked through three CAF flavors before landing on the format Apple's Messages.app actually emits when a user records a native iMessage voice memo:
- PCM int16 @ 44.1 kHz CAF: BlueBubbles' internal `afconvert -f m4af -d aac` conversion fails; the original CAF reaches iMessage but renders with 0 s duration.
- AAC @ 22.05 kHz mono CAF: BlueBubbles' conversion succeeds and the server silently downgrades the delivery, sending the converted MP3 as a generic audio attachment.
- **Opus @ 24 kHz mono CAF**: byte-identical to the descriptor block Apple's Messages.app produces; BlueBubbles passes it through unchanged and iMessage renders a native voice-memo bubble with proper duration and waveform UI.
Adds an opt-in `tts.voice.preferAudioFileFormat` channel capability and a macOS `afconvert`-backed pre-transcode in the speech-core pipeline. BlueBubbles declares `preferAudioFileFormat: "caf"`. Other channels are unaffected. Falls back to the original buffer when the host platform, the source/target pair, or the transcoder process can't produce the preferred container — so non-Darwin hosts and unsupported provider combinations are unchanged.
Also adds a `caff` magic-byte sniff in `src/media/mime.ts` so the auto-reply host-local-media validator (which uses `file-type` and didn't recognize CAF natively) accepts the buffer instead of dropping it as "⚠️ Media failed."
Fixes#72506.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remediate current-profile CodeQL findings for file SecretRef id validation and release workflow job permissions. Includes changelog credit. Thanks @vincentkoc.
Feishu config defaults groupPolicy to 'allowlist'. Inbound group handling read groupAllowFrom and called isFeishuGroupAllowed before resolveFeishuReplyPolicy was reached, so a config that only set channels.feishu.groups.<chat_id>.requireMention=false (with no groupAllowFrom) was rejected with 'group not in groupAllowFrom' before per-group requireMention could take effect. Treat the explicit presence of a group entry under channels.feishu.groups as the operator's allowlist signal: if groupConfig is defined, skip the empty-allowlist rejection. resolveFeishuReplyPolicy still owns mention gating, and existing groupConfig.enabled=false / groupAllowFrom-driven rejections are preserved. Adds a regression test that exercises the reporter's exact config shape and confirms inbound text reaches finalize/dispatch.
The ACP dispatch path calls applyMediaUnderstanding without the agentDir
parameter. This prevents the media understanding pipeline from locating
agent-specific models.json and auth profiles, causing image understanding
to fail silently for non-visual models configured with a separate image
understanding model.
The non-ACP reply path (get-reply.ts) already passes agentDir correctly.
This aligns the ACP path with the same behavior.
Closes#55046
AI-assisted (built with Hermes orchestration).
Address review feedback on PR #72888. triggerInternalHook passes the
same event reference to all handlers sequentially. Mutating evt.context
leaks pluginConfig to subsequent handlers and causes cross-plugin
overwrites. Shallow-copy event and context instead.
When plugins register hooks via api.registerHook(), pluginConfig from
openclaw.json was not available in the hook event context. Plugins that
accessed ctx.pluginConfig or event.context.pluginConfig received
undefined, causing silent failures or fallback to defaults.
Changes:
- Add pluginConfig parameter to registerHook() function
- Wrap handler to inject pluginConfig into event.context before invocation
- Pass params.pluginConfig through createApi() call site
Fixes#72880
Closes#72837. The 15s narrative-subagent timeout was empirically too
tight for warm-gateway runs across light, REM, and deep phases —
gpt-5.4-mini latency through OpenAI alone routinely brushes 12s+, so the
first sweep after a restart deterministically times out across all three
phases. 60s gives realistic LLM-call headroom while still capping the
worst case at one minute, preserving the original comment's "don't leave
parent cron running for minutes" constraint.
Test: updates the matching toMatchObject assertion in
dreaming-narrative.test.ts from 15_000 to 60_000.
When the system hostname exceeds 63 bytes (common with Kubernetes pod
names), the @homebridge/ciao DNS label encoder throws an AssertionError
that crashes the gateway on startup.
Add truncateToDnsLabel() that safely truncates UTF-8 strings at byte
boundaries, applied to both the service instance name and hostname
before passing them to ciao.
Closes#37705
AI-assisted (built with Hermes orchestration).
Commit 2cd23957c0 ("build: use slim docker runtime") switched the
runtime image from `node:24-bookworm` (full) to `node:24-bookworm-slim`.
The slim base does not ship `ca-certificates`, and the runtime stage's
`apt-get install` line was not updated to add it.
Result on the resulting image:
- `/etc/ssl/certs/` is empty (`ls /etc/ssl/certs/ | wc -l` == 0)
- `dpkg -l ca-certificates` reports `un` (not installed)
- `update-ca-certificates` is missing in `$PATH` (exit 127)
- every HTTPS outbound from the gateway dies at TLS handshake with
`error setting certificate file: /etc/ssl/certs/ca-certificates.crt`
- channel plugins that use `node fetch` (telegram/discord/slack)
crash-loop with `Network request for 'deleteWebhook' failed!`
and pin the gateway main thread at ~100% CPU on retry.
Verified by rebuilding the runtime image with this patch and
confirming inside the container:
- `ls /etc/ssl/certs/ | wc -l` -> 285
- `curl -4 https://api.telegram.org/` -> 302
- `curl -4 https://www.google.com/` -> 200
- channel plugins (telegram/discord/slack) register cleanly,
gateway main-thread CPU returns to idle.
Add `ca-certificates` to the apt-install list and call
`update-ca-certificates` to populate the CA bundle.
Signed-off-by: ryuhaneul <luj.moonlight@gmail.com>
When the post-update completion cache refresh times out (slow disk,
large bundled plugin tree, Docker overlayfs), the user previously saw
the opaque 'Completion cache update failed: Error: spawnSync
/usr/bin/node ETIMEDOUT'. Detect ETIMEDOUT specifically, surface
'timed out after 30s', and append a manual refresh hint pointing at
'openclaw completion --write-state' so users know it's non-fatal and
how to recover.
Fixes#72842
Add an end anchor to the type/subtype match and explicitly accept the
RFC 9110 ;parameter tail. Inputs like "image/png<script>" or
"application/json garbage" now return undefined instead of silently
matching the leading prefix.
Closes#9795
Remove the two Unreleased Coven ACP/runtime changelog bullets that were reintroduced after the Coven extension removal.\n\nVerification:\n- rg -n -i "coven" CHANGELOG.md Swabble/CHANGELOG.md extensions/matrix/CHANGELOG.md apps/ios/CHANGELOG.md\n- git diff --check origin/main..HEAD\n- PR checks passed on head 767c274b0f
* fix(ui): discard stale config state on explicit reload
* fix(clownfish): address review for ghcrawl-156594-autonomous-smoke (1)
* fix(clownfish): address review for ghcrawl-156594-autonomous-smoke (1)
* test(ui): align channel config host state
* fix(gateway): preserve runtime-backed health state
* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)
* fix(gateway): harden health snapshot exposure
Both lanes had only one paragraph each in qa-e2e-automation.md. Adds a
"Telegram and Discord QA reference" section verified against
extensions/qa-lab/src/live-transports/{telegram,discord}/* with:
- shared CLI flags table (--scenario, --output-dir, --repo-root, --sut-account,
--provider-mode, --model, --alt-model, --fast, --credential-source,
--credential-role) — none of these were enumerated for either lane.
- Telegram QA: 8 scenario ids
(telegram-canary/-mention-gating/-mentioned-message-reply/-help-command/
-commands-command/-tools-compact-command/-whoami-command/-context-command),
output artifact paths (telegram-qa-report.md, -summary.json,
-observed-messages.json), and the redaction toggle.
- Discord QA: 3 scenario ids
(discord-canary/-mention-gating/-native-help-command-registration), output
artifact paths, and the SUT-application-id-must-match-bot-user-id check.
- Convex credential pool: documents Discord support (only Telegram was
mentioned before) and the per-kind payload shapes for the
admin/add validator. Cross-links to testing.md for the broker endpoint
contract.
Slims the duplicate Operator-flow paragraphs for Telegram and Discord into a
single one-block pointer that links to the new reference section.
Reorg
- Rename the architecture page title to "QA overview" (slug stays
/concepts/qa-e2e-automation so inbound links keep working).
- Move "Adding a channel to QA" + scenario-helper-name reference from
testing.md into qa-e2e-automation.md under "Transport adapters". Architecture
belongs with the architecture page.
- Drop the duplicate live-transport coverage table from testing.md; canonical
copy stays in qa-e2e-automation.md under a new "Live transport coverage"
heading so qa-matrix.md can deep-link to it.
- Slim testing.md QA-specific runners section to ops only, with cross-links.
Audit (against extensions/qa-lab/src/cli.ts, qa-channel/src/config-schema.ts,
and live-transport runtimes)
- qa-e2e-automation.md gains a "Command surface" table covering all 14
openclaw qa <subcommand> forms; previously only ~7 of 14 were named.
- Document missing OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT and
OPENCLAW_QA_DISCORD_CAPTURE_CONTENT env vars (Matrix already had it).
- Cross-link qa coverage from the Reporting section.
- qa-channel.md completes the config-key list (enabled, name, accounts,
defaultAccount were missing from the schema doc) and pollTimeoutMs range.
- Drop stale "Follow-up work" framing in qa-channel.md (provider/model matrix,
scenario discovery, orchestration) — all three already shipped.
- Replace "vertical slice" language with current behavior; fix misplaced
debugger-UI paragraph.
Discoverability
- Add a Note callout to testing.md pointing at the three QA pages
(QA overview, Matrix QA, QA channel) so maintainers landing on testing.md
see the QA stack in the prologue.
Glossary entries for the renamed/new doc titles.
Adds a focused reference for the Docker-backed Matrix QA lane (CLI flags,
seven scenario profiles, eight env vars including the redaction toggle and
Tuwunel image override, scenario taxonomy, output artifact layout, and triage
tips). Source-of-truth checked against extensions/qa-matrix/src/cli.ts,
shared/live-transport-cli.ts, runners/contract/{runtime,scenario-catalog}.ts,
and substrate/harness.runtime.ts.
Registered in docs/docs.json alongside QA E2E automation.
Add the opt-in Coven ACP runtime bridge as a bundled extension while keeping ACPX as the default path.
Security hardening included before merge:
- fail closed by default instead of silently falling back;
- bounded health/socket requests and daemon response sizes;
- fixed Coven socket trust anchor and symlink/path validation;
- reject untrusted harness/session/event ids before exposing them;
- sanitize daemon-controlled terminal/status/error strings;
- use incremental event polling with bounded dedupe state;
- clean up launched Coven sessions before fallback when daemon ids are invalid.
Validation:
- pnpm test extensions/coven/src/config.test.ts extensions/coven/src/client.test.ts extensions/coven/src/runtime.test.ts
- pnpm check:changed
- GitHub CI green on a64eac20b9
- Greptile Review green
Preserve contributor credit and land the narrowed sessions_spawn ACP-field handling with follow-up transcript redaction and ACP resume ownership hardening. Targeted Blacksmith validation passed for the touched sessions/ACP tests.
The talk.config discovery RPC was handing the source-snapshot's
talkProviderConfig (with the unresolved SecretRef wrapper still on
apiKey) to speechProvider.resolveTalkConfig. ElevenLabs/OpenAI's
strict normalizeResolvedSecretInputString helper threw 'unresolved
SecretRef' there, so iOS / macOS / Control UI Talk overlays never
learned the configured provider and silently fell back to local
AVSpeechSynthesizer ('robot voice') even though talk.realtime.session
and talk.speak both worked end-to-end with the same SecretRef.
Prefer the runtime-resolved provider config when calling
resolveTalkConfig, strip the apiKey field if it's still a SecretRef
wrapper at the call site, and restore the source-shaped apiKey onto
the response so the UI keeps the SecretRef context. Redaction strips
the value when includeSecrets=false.
Adds a regression test using a strict resolver speech provider that
mirrors ElevenLabs/OpenAI behavior so the path stays covered for
SecretRef apiKeys.
Fixes#72496
Thanks @omarshahine
Keep Google Live Talk browser sessions on the supported WebSocket/gateway-relay paths instead of falling back to browser WebRTC, remove stale browser-native voice controls that bypass Talk/TTS provider settings, and harden the Google Live URL plus realtime relay resource controls.
Verification:
- pnpm test ui/src/ui/realtime-talk.test.ts ui/src/ui/realtime-talk-google-live.test.ts src/gateway/talk-realtime-relay.test.ts src/gateway/server-methods/talk.test.ts
- pnpm check:changed
* feat(qqbot): implement unified media upload handling and introduce chunked upload support
This commit enhances the media upload functionality by introducing a unified `sendMedia` method that consolidates the previous separate methods for sending images, voice messages, videos, and files. Key changes include:
- Added `uploadChunked` function for future chunked media uploads, currently marked as not implemented.
- Introduced `MediaSource` abstraction to handle various media types (URLs, base64, local files, buffers) uniformly.
- Updated existing media handling logic to utilize the new `sendMedia` method, ensuring consistent media processing across different types.
- Enhanced error handling and validation for media uploads, including MIME type checks and file size limits.
These changes aim to streamline the media upload process and prepare for future enhancements in handling larger files through chunked uploads.
* feat(qqbot): enhance media upload capabilities with chunked upload support
This commit updates the media upload functionality by implementing chunked upload support for larger files. Key changes include:
- Revised the `SKILL.md` documentation to clarify media file size limits and local file path requirements.
- Introduced a new test suite for the chunked media upload functionality, ensuring robust error handling and upload processes.
- Updated the media handling logic to enforce per-file-type upload ceilings, allowing for seamless integration of chunked uploads.
- Enhanced error handling for daily upload limits, providing user-friendly messages when limits are exceeded.
These improvements aim to streamline the media upload process and accommodate larger files effectively.
* feat(qqbot): add C2C streaming API support for message delivery
This commit introduces support for the QQ C2C official `stream_messages` API, enabling single-message typing-style updates. Key changes include:
- Updated the configuration schema to include a new `c2cStreamApi` boolean option for enabling the C2C streaming API.
- Enhanced the `QQBotAccountConfig` interface to accommodate the new streaming option.
- Implemented a `StreamingController` to manage the lifecycle of C2C stream messages, ensuring proper handling of media tags and message boundaries.
- Updated the outbound dispatch logic to utilize the new streaming capabilities, allowing for more dynamic message delivery in one-to-one chats.
These enhancements aim to improve the responsiveness and interactivity of message delivery within the QQBot framework.
* feat(qqbot): implement group chat support and unify adapter/DI architecture
- Implement group message history tracking with pending history buffer
(record on skip, render on @-mention reply)
- Add mention detection and gating: explicit @bot, implicit quote-reply,
ignoreOtherMentions, configurable activation mode (mention/always)
- Add group activation resolution with session store persistence
- Add message queue with per-peer FIFO and group message merging
(batch multiple rapid messages into one merged payload)
- Add deliver debounce to merge rapid outbound text bursts into
single messages, with media flush and maxWait cap
- Add group config resolution: per-group prompt, history limit,
wildcard and specific group overrides
- Enrich history attachments with local paths from processAttachments
so that history context renders downloaded paths instead of ephemeral
QQ CDN URLs
- Merge ports/ directory into adapter/ as single entry point
- Expand EngineAdapters to 5 required ports: history, mentionGate,
audioConvert, outboundAudio, commands
- Remove global register/get singletons in favor of constructor
injection and one-time init
- Add createEngineAdapters() in bridge/gateway.ts as single assembly point
- Extract monolithic buildInboundContext into 11 discrete stages:
access, content, quote, refidx, group-gate, envelope, assembly
- Extract group chat modules: history, mention, activation,
message-gating, deliver-debounce
- Extract config/group.ts, utils/attachment-tags.ts
* feat(qqbot): add /bot-streaming command for C2C message streaming control
This commit introduces the `/bot-streaming` command, allowing users to enable or disable streaming for message delivery in C2C chats. Key changes include:
- Implementation of the `isStreamingConfigEnabled` function to check the current streaming configuration.
- Command handler for `/bot-streaming` that provides usage instructions and manages the streaming state.
- Updates to the command's response messages to inform users of the current streaming status and how to toggle it.
These enhancements aim to improve user experience by providing a straightforward way to manage streaming message delivery in private chats.
* feat(qqbot): extract interaction handler and add remote config query/update support
- Extract INTERACTION_CREATE handler from gateway.ts into a dedicated
interaction-handler.ts module for better separation of concerns
- Add config query (type=2001) and config update (type=2002) interaction
branches that read/write claw_cfg via runtime.config API
- Register INTERACTION intent (1<<26) in FULL_INTENTS to receive
INTERACTION_CREATE events from the gateway
- Add InteractionType constants (CONFIG_QUERY, CONFIG_UPDATE)
- Extend GatewayPluginRuntime with optional config API (loadConfig,
writeConfigFile) for interaction handler access
- Add QQBotAccountConfigView interface for typed config field access
- Extend acknowledgeInteraction to accept optional data payload for
rich ACK responses (e.g. claw_cfg snapshot)
- Export getFrameworkVersion from slash-commands-impl for version
reporting in config snapshots
- Remove unused eslint-disable directive in streaming-media-send.ts
* feat(qqbot): enhance account management and logging capabilities
- Introduced `toGatewayAccount` function to map resolved QQBot accounts to the engine's gateway account structure.
- Added `persistAccountCredentialSnapshot` function to streamline credential backup during gateway events.
- Updated the `qqbotPlugin` to utilize the new account mapping and credential persistence functions, improving the handling of account data.
- Enhanced logging functionality by modifying the `EngineLogger` interface to support metadata in log messages.
- Implemented new commands for managing logs and clearing storage, providing users with better control over their data and system resources.
- Registered multiple built-in commands for improved user interaction, including `/bot-logs` for exporting logs and `/bot-clear-storage` for managing downloaded files.
- Updated configuration schemas to reflect new options and improve clarity for users.
* fix(qqbot): resolve oxlint errors and update raw-fetch allowlist
- Replace unnecessary `else` after `return` in outbound-media-send.ts (6 occurrences)
- Use `Number.parseInt` instead of global `parseInt` in outbound.ts and streaming-media-send.ts
- Use `Number.isNaN` instead of global `isNaN` in register-basic.ts
- Prefer `**` over `Math.pow` in media-chunked.ts
- Convert interface with call signature to function type in commands.port.ts
- Update api-client.ts allowlist line number (108→124) and add media-chunked.ts:552 to raw-fetch allowlist
* docs(qqbot): translate streaming-c2c.ts header comments to English
* feat(qqbot): add voiceMediaTypes
* feat: restore dispatch changes
* fix(qqbot): align test files with updated engine interfaces after rebase
- inbound-attachments.test: replace removed registerAudioConvertAdapter
with AudioConvertPort, pass audioConvert in ProcessContext
- inbound-pipeline.self-echo.test: add required adapters field to
InboundPipelineDeps mock (history, mentionGate, audioConvert,
outboundAudio, commands)
- outbound-dispatch.test: add required skipped field to InboundContext
* fix(qqbot): update test assertions to match refactored engine interfaces
- inbound-pipeline.self-echo.test: self-echo blocking was moved upstream;
update test to expect non-blocked pipeline behavior
- outbound-dispatch.test: TTS voice path now uses unified sendMedia
instead of sendVoiceMessage; add sendMedia mock and update assertion
- format-ref-entry.test: attachment format changed from [image: ...]
to MEDIA: tag syntax via renderAttachmentTags; update expected output
* refactor(qqbot): migrate from deprecated config API to current/replaceConfigFile
Replace all usages of deprecated runtime config methods:
- loadConfig() → current()
- writeConfigFile(cfg) → replaceConfigFile({ nextConfig, afterWrite })
Updated files:
- bridge/narrowing.ts: writeOpenClawConfigThroughRuntime
- adapter/commands.port.ts: ApproveRuntimeGetter type signature
- commands/builtin/register-approve.ts: loadExecConfig, writeExecConfig, reset
- commands/builtin/register-streaming.ts: config read/write
- gateway/interaction-handler.ts: config query/update handlers
- gateway/types.ts: GatewayPluginRuntime.config interface
* feat(qqbot): update package.json
* fix(qqbot): replace deprecated config-runtime import with config-types subpath
Bundled plugin lint requires focused plugin-sdk subpaths.
- gateway.ts: openclaw/plugin-sdk/config-runtime → config-types
- narrowing.ts: openclaw/plugin-sdk/config-runtime → config-types
* feat(qqbot): group chat support, C2C streaming, chunked media upload, and architecture refactor (#70624) (thanks @cxyhhhhh)
---------
Co-authored-by: Bobby <zkd8907@live.com>
Co-authored-by: sliverp <870080352@qq.com>
## Summary
- render cron job prompts and run summaries through the sanitized markdown pipeline in the Control UI
- keep system-event cron payloads plain and prevent markdown link clicks from triggering row selection
- handle failed runs with missing or empty summaries without duplicating or hiding the error text
## Verification
- pnpm test ui/src/ui/views/cron.test.ts
- pnpm test src/plugins/doctor-contract-registry.test.ts src/plugins/setup-registry.test.ts
- pnpm check:changed
- GitHub CI green on 251f01a3b0
Trace to edb3e84898 (fix: clean stale plugin channel config). When
openclaw doctor --fix removes a missing channel plugin, it also cascades
the cleanup to dangling channel config, heartbeat targets, and channel
model overrides, preventing gateway boot loops after failed plugin
reinstalls. Added an Accordion 11d to docs/gateway/doctor.md listing the
exact config keys that get pruned alongside the plugin entry.
Fixes#68160.
Drops stale optionality from the hello-ok auth schema and keeps generated Swift models, macOS fixtures, browser client types, protocol docs, and merged-base test boundaries aligned.
Harden WebChat input history handling so draft, navigation, and render-state behavior stay consistent across the chat UI.
Validated locally on the rebased PR head 742a5f22f1:
- CI=true OPENCLAW_LOCAL_CHECK=0 pnpm check:changed
- CI=true OPENCLAW_LOCAL_CHECK=0 pnpm test:changed
Closes#38702.
Trace to fee16865b2 (fix(agents): accept LAN local auth markers) and the
companion 0dd2844991 (fix: preserve Ollama local marker auth). The fix
extends ollama-local marker handling to any custom OpenAI-compatible
provider whose baseUrl resolves to loopback, a private LAN, .local, or a
bare hostname, so persisted local markers no longer fail with missing-auth
errors for non-Ollama-typed local providers (LM Studio, vLLM, LiteLLM).
The Ollama provider page already covers ollama-local for Ollama-typed
providers; this note lives in docs/gateway/local-models.md where custom
OpenAI-compatible local stacks are documented.
The publish workflow rsyncs source docs/ into the publish repo with --delete,
but explicitly protects locale directories so translation files survive
non-translation-pipeline syncs. When an English source file is renamed (for
example install/migrating-matrix.md -> channels/matrix-migration.md), the
locale copies at <locale>/install/migrating-matrix.md become orphans:
deleted from the English nav but still present on disk.
Mintlify's hosted build appears to silently fall back to the previous
deployment when nav references a path with mixed locale availability, so
recent docs changes (the migration hub rework, matrix-migration move) are
not propagating to docs.openclaw.ai even though every CI run reports
success and the publish repo has the right English content.
Add a pruneOrphanLocaleDocs() pass that walks every generated-locale
directory in the publish target and removes any .md/.mdx file whose
matching English path no longer exists in source docs. Runs after rsync
and before composing docs.json so the regenerated nav and the on-disk
files stay consistent. Verified the logic against the live publish repo:
identifies all ja-JP/es/pt-BR/ko/de/fr/ar/it/tr/uk/id/pl/zh-CN orphans of
install/migrating-matrix.md (12 entries) and would also catch any future
renames the same way.
The Matrix migration guide is plugin-upgrade content (encrypted-state recovery,
device verification, room-key restore) rather than a cross-system import or
machine move, so it belongs alongside the Matrix channel docs rather than under
Install > Maintenance > Migrating.
- Move docs/install/migrating-matrix.md to docs/channels/matrix-migration.md
- Update inbound link in docs/channels/matrix.md
- Update the migrating.md hub: replace the Matrix Card with a one-line link in 'Upgrade a plugin in place'
- Refresh Related list on the moved page (link Matrix push rules and Migration guide hub)
- docs.json: remove install/migrating-matrix from Maintenance > Migrating, slot channels/matrix-migration between channels/matrix and channels/matrix-push-rules in the Mainstream channels group, and add a /install/migrating-matrix -> /channels/matrix-migration redirect
- install/migrating: convert to a hub page with three clear paths (CardGroup for cross-system imports linking Claude+Hermes, machine-to-machine move with Steps and AccordionGroup, plugin upgrade Card linking Matrix)
- install/migrating-claude: align with Hermes page structure (add Restart-and-verify Step, JSON output for automation, Troubleshooting AccordionGroup with 4 entries, cross-link to Hermes guide)
- cli/migrate: tighten intro to mention both bundled providers and link the migration hub
- docs.json: move Maintenance group to immediately after Install overview, nest the four migrating pages (migrating, migrating-claude, migrating-hermes, migrating-matrix) under a 'Migrating' subgroup so they collapse into a dropdown
Adds a raw config pending-changes diff panel in Control UI raw mode, with JSON5 parsing, sensitive-value redaction until explicit reveal, bounded diff work, and tests for redaction/reveal and stale reveal-state reset.
Also aligns provider manifest contract coverage for google-vertex and Qwen aliases to unblock the rebased CI matrix.
Supersedes stale PRs #48621 and #46654. PR #48621 had gone stale without maintainer follow-up, so this maintainer-authored PR carries the implementation forward transparently while preserving changelog credit for the original contributor and @BunsDev.
* fix(cli): keep nodes list aligned with nodes status
* fix(clownfish): address review for ghcrawl-156588-autonomous-smoke (1)
* fix(cli): keep nodes list aligned with nodes status
Trace to b642ebece9 (fix(feishu): do not treat @all as a bot mention).
Document the new behavior in the mention requirement section: broadcast-only
@all/@_all messages no longer wake the bot, while messages that combine @all
with a direct bot mention still count as a bot mention.
Add a bundled Claude migration provider for Claude Code and Claude Desktop imports.\n\nIncludes source discovery, preview/apply behavior for instructions, MCP servers, skills and command prompts, archive/manual handling for unsafe Claude state, docs, labeler, and tests.
Display friendly agent identity labels in the Control UI Sessions key column when identity data is available, keep raw-key fallback behavior, and allow filtering by agent identity name.
This is the maintainer-owned replacement for #54212 by @dingtao416. Thanks @dingtao416 for the original feature idea and implementation direction.
Includes follow-up fixes from maintainer review automation: normalized key-cell classes, own-property identity lookup, and friendly-label tooltips.
Validation:
- pnpm test ui/src/ui/format.test.ts ui/src/ui/views/sessions.test.ts
- pnpm check:changed
Closes#54163.
Supersedes #54212.
Require Control UI updates to observe a real gateway process replacement, surface skipped/error update outcomes, and verify the running gateway version after restart.\n\nAdds update.status restart-sentinel plumbing, docs, generated protocol model updates, and changelog attribution.\n\nLocal verification:\n- pnpm test src/gateway/server-methods/update.test.ts src/cli/gateway-cli/run-loop.test.ts src/infra/restart-sentinel.test.ts src/infra/process-respawn.test.ts src/infra/update-runner.test.ts ui/src/ui/app-gateway.node.test.ts ui/src/ui/controllers/config.test.ts\n- git diff --check\n- pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/gateway/protocol.md docs/gateway/configuration.md docs/web/control-ui.md\n- pnpm docs:check-mdx
Trace to 8bdfa58cbb (fix(migrations): avoid partial Hermes config apply after
conflict). Hermes apply now marks remaining dependent config items as
"blocked by earlier apply conflict" when a conflict surfaces mid-apply,
instead of writing them partially. Document the user-visible reason string
and where to find blocked items in the migration report.
Routes stateful Google Meet tool actions through the gateway-owned runtime so create/join/status/speak/leave share the same session owner instead of losing tool-created realtime sessions after the agent turn.
Also preserves structured gateway error details for missing session ids and tightens node-host child cleanup for already-closed sessions.
Fixes#72440.
Co-authored-by: BSnizND <199837910+BsnizND@users.noreply.github.com>
Require explicit confirmation before applying restart-impacting Dreaming mode changes in the Control UI.
- Add pending/confirm/loading state for the Dreaming toggle path
- Render a restart confirmation dialog before sending the config patch
- Sync Control UI locale metadata and cover the confirmation flow in browser tests
Fixes#63804
- cli/migrate: convert flat reference into structured Mintlify page (Tip pointer, ParamField for flags, AccordionGroup for safety model, sub-sections for Hermes provider with what's imported, .env keys, archive-only state, and plugin contract)
- install/migrating-hermes: new dedicated user guide modeled after migrating-matrix.md (Tabs for onboarding vs CLI, AccordionGroup for what gets imported, Steps for recommended flow, Warning for --overwrite, Troubleshooting accordions)
- docs.json: add install/migrating-hermes to Maintenance group alongside migrating and migrating-matrix
Remote proof:
- CI run 24982271745 passed on 6122e13c9f.
- Blacksmith Testbox tbx_01kq6vwehcszjfpp52f0pb3v1q passed focused Google Meet formatting, docs/link checks, realtime consult runtime tests, Google Meet tests, extension test typecheck, the core-unit-fast-support shard, and the core support boundary shard.
Thanks @BsnizND.
Co-authored-by: BSnizND <199837910+BsnizND@users.noreply.github.com>
Fixes#72523.
Remote proof:
- CI run 24980529154 passed on 29f825bea5.
- Blacksmith Testbox tbx_01kq6tsgbaxgstxmtearwy9n4w passed focused formatting, Google Meet tests, Google realtime provider tests, and extension test typecheck.
Thanks @BsnizND.
Co-authored-by: BSnizND <199837910+BsnizND@users.noreply.github.com>
Trace to db09f68ce5 (Support SecretRef for voice-call credentials and bundled
plugin SecretInputs #72607). The reference page docs/reference/secretref-credential-surface.md
listed the new entries in the same SHA, but docs/plugins/voice-call.md showed
only plain-string credentials without pointing to the SecretRef surface.
- platforms/android: blockquote Note for Android app status, Note for canvas host port
- platforms/macos: Tip component for app vs CLI discovery comparison
- plugins/zalouser, channels/zalouser: blockquote Warning components for unofficial automation risk
- channels/pairing: convert two Important paragraphs to Note components for DM-vs-group scope and silent-upgrade behavior
- start/openclaw: workspace-as-memory Tip component
- automation/tasks: drop 'this page covers' filler in Note
- automation/auth-monitoring, clawflow, cron-vs-heartbeat: collapse 'this page moved... See X' redirects to single direct sentences
- testing-live: Tip components for model-discovery and authoritative-list guidance
- debugging: --dev flag Note and non-dev gateway stop Tip
- testing: narrowing live tests Tip
- tools/lobster: optional-plugin allowlist Note
- tools/acp-agents-setup: blockquote Important to Warning component
- memory: replace en-dash list separators with em-dashes, sentence-case Further reading link titles
- messages: rewrite filler 'this page ties together' opener to a direct one
- delegate-architecture: convert 4 blockquote security warnings to Warning and Note components
- system-prompt: convert blockquote daily-memory note to Note component
- channels: convert Tip prose to component, fix /channels/index link, sentence-case heading
- configure: convert Note and Tip prose to components
- devices: convert Note and Warning prose to components
- models: sentence-case scan/status subheadings
- agents: clean up related links and Title Case body link
Add the legacy `models.providers.*.api: "openai"` → `"openai-completions"`
migration to doctor's Current migrations list, and note the gateway startup
behavior that skips providers with future or unknown api enum values instead
of failing closed.
Traces to:
- 6a7980e984 fix(doctor): migrate legacy OpenAI provider api
- 147f4f50f5 fix(gateway): skip stale model provider api entries
Polish the Control UI quick settings dashboard layout.
- Rework quick settings into a 12-column desktop grid with matched top-row card heights.
- Pair Personal with a right-side Appearance/Automations stack on large screens while preserving tablet/mobile ordering.
- Add render/style guards plus an Unreleased changelog entry crediting @BunsDev.
Validated with focused UI tests, formatting, git diff checks, local changed gate, and full PR CI.
Add manifest-owned GitHub Copilot token support for non-interactive onboarding, including documented env fallback, ref-mode tokenRef storage, saved-profile reuse, and default model wiring that preserves existing primary model configuration.
Validation:
- pnpm test extensions/github-copilot/index.test.ts src/plugins/contracts/registry.contract.test.ts src/commands/onboard-non-interactive/local/auth-choice-inference.test.ts
- pnpm check:changed
- CI green on aadac2c8d4
Resolve Feishu group chat labels through getChatInfo so session labels prefer human-readable group names over raw chat IDs.\n\nPreserve topic/thread label priority and defer the lookup until after broadcast dedup claims to avoid duplicate account API calls.\n\nValidation:\n- pnpm test extensions/feishu/src/bot-group-name.test.ts extensions/feishu/src/bot.broadcast.test.ts\n- pnpm check:changed\n- GitHub CI green on c154dc0a41fd715dce95ef1fb5d0c269533b8c22\n\nCloses #35675
Address Clownfish follow-up on Telegram native draft finalization. Requires real streamed assistant partials before materializing drafts, clears stale native draft previews, and keeps media/buttons on normal send path.
Fixes the post-merge review follow-ups from #72471 by deduping stale pre-compaction state entries and preserving parent-before-child ordering for successor transcripts.
Add command-level sentinel coverage proving channel setup metadata, onboarding auth choices, and models-list provider ownership stay on manifest/registry paths without importing plugin runtime.\n\nLocal verification:\n- pnpm exec oxfmt --check --threads=1 src/commands/plugin-control-plane-cold-imports.test.ts\n- OPENCLAW_LOCAL_CHECK_MODE=throttled pnpm test:serial src/commands/plugin-control-plane-cold-imports.test.ts\n- OPENCLAW_LOCAL_CHECK_MODE=throttled pnpm check:changed\n- clean rebase sanity: git diff --check origin/main...HEAD\n\nPR CI had known unrelated main-red failures matching latest main run 24970053892; the new sentinel test passed in CI.
* fix(telegram): send fresh finals for stale previews
* test(telegram): cover stale preview send fallback
* fix(telegram): keep stale archived preview fallback
* fix(telegram): clear stale active previews
* fix(telegram): reset preview state after fresh finals
Fixes #70678.\n\nKeeps quiet but healthy WhatsApp linked-device sessions connected by tracking WhatsApp Web transport activity, while retaining a longer app-silence cap so frame activity cannot mask a stuck session forever. Also cleans up transport activity listeners on failed connection-open paths.\n\nCarries forward the focused #71466 approach and keeps #63939 as related configurable-timeout follow-up. Thanks @vincentkoc and @oromeis.\n\nValidation:\n- pnpm test:serial extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts extensions/whatsapp/src/connection-controller.test.ts\n- pnpm check:changed\n- codex review --base origin/main
* 'main' of https://github.com/openclaw/openclaw:
fix(plugins): satisfy doctor compat lint
chore(plugins): inventory doctor deprecation compat
fix(plugins): record crabpot compat deprecations
docs(dreaming): rewrite with AccordionGroup for phases and backfill, Tabs for quick start and CLI workflow, ParamField for dreaming defaults
Review feedback from @chatgpt-codex-connector (P1): callers that pass
`tryNative: false` rely on jiti's alias rewriting (e.g.
`bundled-capability-runtime` in Vitest+dist mode narrows the SDK
slice through shim aliases). Route everything through the jiti
loader when `tryNative` is false so those rewrites still apply.
Review feedback from @greptile-apps (P2): forward the full argument
tuple through to the jiti fallback with `...rest` so any future
loader option argument is not silently dropped by the wrapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every CLI invocation reads the config snapshot, which pulls bundled
channel doctor contracts and setup surfaces through
`getCachedPluginJitiLoader`. jiti's TS→JS transform pipeline adds
several seconds of per-load overhead on slower hosts (NAS profiling
shows ~78% of `openclaw config get` wall time spent inside the jiti
library), and that overhead is pure waste for the already-compiled
`.js` artifacts shipped in dist/.
Wrap the loader returned by `getCachedPluginJitiLoader` so that
compiled JS targets go through `tryNativeRequireJavaScriptModule`
first. Jiti stays on the hot path for:
- TS/TSX/MTS/CTS sources
- paths the native-require helper declines (Windows by default, or
module-resolution fallbacks)
This centralises the fast path that already existed — inside
`doctor-contract-registry` and `channel-entry-contract` — and extends
it to every caller that goes through the jiti loader cache.
Benchmark on a modest NAS (Node 22.22, ZFS, telegram + discord
configured):
| command | before | after |
|------------------|-------:|------:|
| config get X | 24s | 6s |
| status | 45s | 18s |
| devices list | 55s | 26s |
| nodes status | 55s | 26s |
Fixes the slow config/status/devices/nodes read paths reported in
openclaw#62842. Remaining time is dominated by non-jiti code paths
(config schema validation, eager provider-plugin module eval) that
are out of scope for this patch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keep web-search configure and channel command defaults on cold plugin metadata, harden persisted registry reads, and require active config for manifest command defaults.\n\nThanks @vincentkoc
description: Use gitcrawl for OpenClaw issue and PR archive search, duplicate discovery, related-thread clustering, and local GitHub mirror freshness checks.
metadata:
openclaw:
requires:
bins:
- gitcrawl
---
# Gitcrawl
Use this skill before live GitHub search when triaging OpenClaw issues or PRs.
`gitcrawl` is the local candidate-discovery layer. It is fast, includes open and closed threads, and can surface duplicate attempts, related issues, and already-landed fixes. It is not the final source of truth for comments, labels, merges, closes, or current CI.
- Treat `gitcrawl` as stale if `doctor` shows no target thread, an old `last_sync_at`, missing embeddings for neighbor/search commands, or a clearly wrong open/closed state.
- If stale data blocks the decision, refresh the portable store first:
- Run expensive update commands such as `gitcrawl sync --include-comments` only when the user asked to update the local store or stale data is blocking the decision.
- The sync default is all GitHub thread states; pass `--state open`, `--state closed`, or `--state all` only when a task requires a narrower or explicit scope.
## Boundaries
- Use `gitcrawl` for candidates, clusters, and historical context.
- Use `gh`, `gh api`, and the current checkout for live state before commenting, labeling, closing, reopening, merging, or filing a PR review.
- Do not close or label based only on `gitcrawl` similarity. Require matching problem intent plus live verification.
- If `gitcrawl` is unavailable, say so and fall back to targeted `gh search` rather than blocking normal maintainer work.
short_description:"Search local OpenClaw issue and PR history before live GitHub triage"
default_prompt:"Use $gitcrawl to inspect OpenClaw issue and PR history, find related threads and duplicate candidates, then verify actionable decisions with live GitHub."
@@ -7,20 +7,21 @@ description: Review, triage, close, label, comment on, or land OpenClaw PRs/issu
Use this skill for maintainer-facing GitHub workflow, not for ordinary code changes.
## Start issue and PR triage with ghcrawl
## Start issue and PR triage with gitcrawl
-Anytime you inspect OpenClaw issues or PRs, check local `ghcrawl` data first for related threads, duplicate attempts, and already-landed fixes.
-Use`ghcrawl`for candidate discovery and clustering; use `gh`, `gh api`, and the current checkout to verify live state before commenting, labeling, closing, or landing.
-If`ghcrawl`is missing, stale, lacks the target thread, or has no embeddings for neighbor/search commands, fall back to the GitHub search workflow below.
-Do not run expensive/update commands such as `ghcrawl refresh`, `ghcrawl embed`, or `ghcrawl cluster` unless the user asked to update the local store or the stale data is blocking the decision.
-Use `$gitcrawl` first anytime you inspect OpenClaw issues or PRs.
-Check local`gitcrawl`data first for related threads, duplicate attempts, and already-landed fixes.
-Use`gitcrawl`for candidate discovery and clustering; use `gh`, `gh api`, and the current checkout to verify live state before commenting, labeling, closing, or landing.
-If `gitcrawl` is missing, stale, lacks the target thread, or has no embeddings for neighbor/search commands, fall back to the GitHub search workflow below.
- Do not run expensive/update commands such as `gitcrawl sync --include-comments`, future enrichment commands, or broad reclustering unless the user asked to update the local store or stale data is blocking the decision.
- Prefer `ghcrawl` first. Then use targeted GitHub keyword search to verify gaps, live status, comments, and candidates not present in the local store.
- Prefer `gitcrawl` first. Then use targeted GitHub keyword search to verify gaps, live status, comments, and candidates not present in the local store.
- Use `--repo openclaw/openclaw` with `--match title,body` first when using `gh search`.
- Add `--match comments` when triaging follow-up discussion or closed-as-duplicate chains.
- Do not stop at the first 500 results when the task requires a full search.
default_prompt:"Use $openclaw-pre-release-plugin-testing to plan or run pre-release OpenClaw plugin validation across package, lifecycle, doctor, gateway, SDK, and live-ish proof."
short_description:"Benchmark and fix slow OpenClaw tests"
default_prompt:"Use $openclaw-test-performance to reassess the OpenClaw test benchmark, identify the next real hotspot, fix it without losing coverage, update the report, and commit scoped changes."
short_description:"Benchmark tests, plugin suites, CPU, RSS, and heap growth"
default_prompt:"Use $openclaw-test-performance to reassess OpenClaw test and plugin-suite performance, collect wall/import/CPU/RSS metrics, investigate memory growth when needed, fix the next real hotspot without losing coverage, update the report, and commit scoped changes."
description: Choose, run, rerun, or debug OpenClaw tests, CI checks, Docker E2E lanes, release validation, and the cheapest safe verification path.
---
# OpenClaw Testing
Use this skill when deciding what to test, debugging failures, rerunning CI,
or validating a change without wasting hours.
## Read First
-`docs/reference/test.md` for local test commands.
-`docs/ci.md` for CI scope, release checks, Docker chunks, and runner behavior.
- Scoped `AGENTS.md` files before editing code under a subtree.
## Default Rule
Prove the touched surface first. Do not reflexively run the whole suite.
1. Inspect the diff and classify the touched surface:
- source: `pnpm changed:lanes --json`, then `pnpm check:changed`
- tests only: `pnpm test:changed`
- one failing file: `pnpm test <path-or-filter> -- --reporter=verbose`
- workflow-only: `git diff --check`, workflow syntax/lint (`actionlint` when available)
- docs-only: `pnpm docs:list`, docs formatter/lint only if docs tooling changed or requested
2. Reproduce narrowly before fixing.
3. Fix root cause.
4. Rerun the same narrow proof.
5. Broaden only when the touched contract demands it.
## Guardrails
- Do not kill unrelated processes or tests. If something is running elsewhere, treat it as owned by the user or another agent.
- Do not run expensive local Docker, full release checks, full `pnpm test`, or full `pnpm check` unless the user asks or the change genuinely requires it.
- Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets.
- Use `scripts/committer "<msg>" <paths...>` when committing; stage only your files.
- If deps are missing, run `pnpm install`, retry once, then report the first actionable error.
- For Blacksmith Testbox proof, reuse only an id warmed and claimed in this
operator session. `blacksmith testbox list` is diagnostics only; a listed id
can have a local key and still carry stale rsync state from another lane.
After warmup, run `pnpm testbox:claim --id <id>`, then prefer
`pnpm testbox:run --id <id> -- "<command>"` for OpenClaw gates so stale
org-visible ids fail fast before syncing. Claims older than 12 hours are
stale unless `OPENCLAW_TESTBOX_CLAIM_TTL_MINUTES` is explicitly set for long
work.
## Local Test Shortcuts
```bash
pnpm changed:lanes --json
pnpm check:changed # changed typecheck/lint/guards; no Vitest
default_prompt:"Use $openclaw-testing to choose the cheapest safe test or CI verification path, inspect failures, and rerun only the relevant OpenClaw lane."
If `uvx --from pr-search-cli pr-search ...` fails because `uvx` or the `pr-search` launcher is not available, ask the user to make that command work before continuing.
If `prtags auth status` shows that the user is not logged in, ask the user to run:
```bash
@@ -90,19 +67,19 @@ Resume only after the missing tool or login state has been fixed.
## Read-Path Default
For read-only GitHub operations in this workflow, use `ghr` as the default CLI.
Treat it as a drop-in replacement for the `gh` read operations you would normally use for PRs, issues, comments, reviews, and duplicate-search evidence.
For candidate discovery in this workflow, use `gitcrawl` first.
Treat it as the local history and clustering layer for related issues, duplicate attempts, and closed threads.
Only fall back to `gh` when `ghr` is failing for a concrete reason, such as:
Use live `gh` or `gh api` for the target thread and for any candidate before making an actionable judgment.
Use live GitHub when `gitcrawl` is missing or stale for a concrete reason, such as:
- the mirrored object is not present yet
- the mirror data is clearly stale or incomplete for the decision you need to make
-the `ghr` command errors, times out, or does not expose the specific read you need
- the target or candidate is not present yet
- the local data is clearly stale or incomplete for the decision you need to make
-`gitcrawl` errors, times out, or lacks the needed neighbor/search data
When you fall back to `gh`, note that you did so and why.
When you fall back to live GitHub search, note that you did so and why.
If `ghr` is missing a fresh PR or issue but `gh` can read it, you may use `gh` for the read-side judgment.
If a later `prtags` target-level write fails because the same object is still missing from `ghreplica`, stop and report that the mirror has not caught up yet instead of forcing the write.
If a later `prtags` target-level write fails because its own mirror has not caught up, stop and report that the curation backend is missing the target object instead of forcing a fallback write.
## Goal
@@ -118,14 +95,12 @@ For each target PR or issue:
Use the tools with these boundaries:
-`ghreplica` is the raw evidence source
- use `ghr` first for normal GitHub read operations in this workflow
-use it for title/body/comment search, related PRs, overlapping files, overlapping ranges, and current PR or issue status
-resort to `gh` only when `ghr` cannot provide the needed read cleanly
-`pr-search-cli` is candidate generation and ranking
- use it to suggest likely duplicate PRs or issue-cluster context
- do not treat it as final truth
- do not create or expand a duplicate group only because `pr-search-cli` put multiple PRs in the same issue or duplicate cluster
-`gitcrawl` is candidate generation and historical context
- use it first for local title/body search, neighbors, clusters, and closed-thread discovery
-treat every candidate as a lead until live GitHub confirms it
-`gh` is live GitHub truth
-use it for target state, body, comments, reviews, files, linked issues, and current open/closed/merged status
- use `gh search` only when `gitcrawl` is stale, missing data, or cannot express the needed query
-`prtags` is the maintainer curation layer
- use it to create or reuse one duplicate group
- use it to save the duplicate status, confidence, rationale, and group summary
@@ -182,7 +157,7 @@ Examples:
## Evidence Checklist
Before declaring a duplicate, gather evidence from at least two categories.
Same-issue or same-cluster output from `pr-search-cli` counts only as candidate generation, not as one of the required proof categories by itself.
`gitcrawl` neighbors, search hits, and cluster membership count as candidate generation, not as enough proof by themselves.
For PRs:
@@ -205,21 +180,18 @@ If you only have wording similarity, that is not enough.
## Step 1: Read The Target
Start by reading the target itself.
Use `ghr` first for this step even if you would normally reach for `gh`.
Do not widen an existing group just because `pr-search-cli` placed several PRs under the same issue or duplicate cluster.
Do not widen an existing group just because `gitcrawl` placed several PRs or issues near each other.
Confirm that the actual implementation path and maintainer intent still match before adding the new member.
Create a new group only when no existing group clearly fits.
@@ -423,8 +377,8 @@ prtags annotation group set <group-id> \
When the evidence is incomplete, set `duplicate_status=candidate` and lower the confidence.
If a per-PR or per-issue annotation write fails because `prtags` cannot resolve the target through `ghreplica`, do not force a fallback write path.
Keep the group state you were able to write, report that the mirror is still missing the target object, and defer the target-level annotation until `ghreplica` catches up.
If a per-PR or per-issue annotation write fails because `prtags` cannot resolve the target, do not force a fallback write path.
Keep the group state you were able to write, report that the curation backend is still missing the target object, and defer the target-level annotation until `prtags` catches up.
short_description:"Find duplicate PRs and issues, group them in prtags, and let prtags sync the GitHub comment"
default_prompt:"Use $tag-duplicate-prs-issues to decide whether an OpenClaw PR or issue is a duplicate, gather evidence with ghreplica and pr-search-cli, group related items in prtags, and save the duplicate judgment."
short_description:"Find duplicate PRs and issues with gitcrawl, group them in prtags, and let prtags sync the GitHub comment"
default_prompt:"Use $tag-duplicate-prs-issues to decide whether an OpenClaw PR or issue is a duplicate, gather candidates with gitcrawl, verify live state with GitHub, group related items in prtags, and save the duplicate judgment."
--arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed; the parent summary re-checks current child run conclusions." \
description:Existing release tag or current full 40-character workflow-branch commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
description:Branch, tag, or full commit SHA to validate
required:true
type:string
expected_sha:
description:Optional full SHA that ref must resolve to
required:false
default:""
type:string
provider:
description:Provider lane for cross-OS onboarding and the end-to-end agent turn
required:false
@@ -25,28 +30,59 @@ on:
- fresh
- upgrade
- both
release_profile:
description:Release coverage profile for live/Docker/provider breadth
required:false
default:full
type:choice
options:
- minimum
- stable
- full
rerun_group:
description:Release check group to run
required:false
default:all
type:choice
options:
- all
- install-smoke
- cross-os
- live-e2e
- package
- qa
- qa-parity
- qa-live
live_suite_filter:
description:Optional exact live suite id for focused live/E2E reruns; blank runs all selected live suites
if [[ -n "${RELEASE_LIVE_SUITE_FILTER// }" ]]; then
echo "- Live suite filter: \`${RELEASE_LIVE_SUITE_FILTER}\`"
fi
echo "- This run will execute cross-OS release validation, install smoke, QA Lab parity, Matrix, and Telegram lanes, and the non-Parallels Docker/live/openwebui coverage from the CI migration plan."
- Owner boundary: fix owner-specific behavior in the owner module. Shared/core gets generic seams only; no owner ids, dependency strings, defaults, migrations, or recovery policy. If a bug names an extension or its dependency, start in that extension and add a generic core seam only when multiple owners need it.
- Legacy config repair: doctor/fix paths, not startup/load-time core migrations.
- Core test asserting extension-specific behavior: move to owner extension or generic contract test.
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
- Formatting: use `oxfmt`, not Prettier. Prefer`pnpm format:check` / `pnpm format`; for targeted files use `pnpm exec oxfmt --check --threads=1 <files...>` or `pnpm exec oxfmt --write --threads=1 <files...>`.
- Linting: use repo wrappers (`pnpm lint:*`, `scripts/run-oxlint.mjs`); do not invoke generic JS formatters/lints unless a repo script uses them.
- Heavy checks: `OPENCLAW_LOCAL_CHECK=1`, mode `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; CI/shared use `OPENCLAW_LOCAL_CHECK=0`.
-Local first. Use repo `pnpm` lanes before Blacksmith/Testbox. Remote only for parity-only failures, secrets/services, or explicit ask.
-Blacksmith/Testbox: on maintainer machines with Blacksmith access, broad/shared validation defaults to Testbox. This includes `pnpm check`, `pnpm check:changed`, `pnpm test`, `pnpm test:changed`, Docker/E2E/live/package/build gates, and any command likely to fan out across many Vitest projects. Do not start those broad gates locally unless the user explicitly asks for local proof or sets `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`.
- Local validation: targeted edit loops only, such as `pnpm test <specific-file>`, targeted formatter checks, and small lint/type probes. If a local command expands beyond targeted proof, stop it and move the broad gate to Testbox.
- Testbox use: run from repo root, pre-warm early with `blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90`, reuse the returned `tbx_...` id for all `run`/`download` commands, and stop boxes you created before handoff. Timeout bins: `90` minutes default, `240` multi-hour, `720` all-day, `1440` overnight; anything above `1440` needs explicit approval and cleanup.
- Testbox full-suite profile: `blacksmith testbox run --id <ID> "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test"`. For installable package proof, prefer the GitHub `Package Acceptance` workflow over ad hoc Testbox commands.
## GitHub / CI
- Triage: list first, hydrate few. Use bounded `gh --json --jq`; avoid repeated full comment scans.
- Automatic PR/issue discovery: skip maintainer-owned items unless directly relevant. Do not comment, close, label, retitle, rebase, fix up, or land them without Peter asking.
- PR scan/triage: no unsolicited PR comments/reviews. Report in chat only unless explicitly asked, or a close/duplicate action needs a reason comment.
- GitHub search boolean text is fussy. If `OR` queries return empty, split exact terms and search title/body/comments separately before concluding no hits.
- PR shortlist: `gh pr list ...`; then `gh pr view <n> --json number,title,body,closingIssuesReferences,files,statusCheckRollup,reviewDecision`.
- GH comments with markdown backticks, `$`, or shell snippets: avoid inline double-quoted `--body`; use single quotes or `--body-file`.
- PR execution artifacts/screenshots: attach them to the PR, comment, or an external artifact store. Do not add `.github/pr-assets` or other PR-only assets to the repo.
- PR review answer must explicitly cover: what bug/behavior we are trying to fix; PR/issue URL(s) and affected endpoint/surface; whether this is the best possible fix, with high-certainty evidence from code, tests, CI, and shipped/current behavior.
- When working on an issue or PR, always end the user-facing final answer with the full GitHub URL.
- CI polling: exact SHA, needed fields only. Example: `gh api repos/<owner>/<repo>/actions/runs/<id> --jq '{status,conclusion,head_sha,updated_at,name,path}'`.
- Post-land wait: minimal. Exact landed SHA only. If superseded on `main`, same-branch `cancel-in-progress` cancellations are expected; stop once local touched-surface proof exists. Never wait for newer unrelated `main` unless asked.
- public SDK/plugin contract: extension prod/test too
- unknown root/config: all lanes
- Before handoff/push: `pnpm check:changed`. Tests-only: `pnpm test:changed`. Full prod sweep: `pnpm check`.
- Before handoff/push for code/test/runtime/config changes: run `pnpm check:changed` in Testbox by default on maintainer machines. Tests-only: run`pnpm test:changed` in Testbox by default. Full prod sweep: run`pnpm check` in Testbox. Use local only for narrow targeted proof or when explicitly requested.
- If `pnpm test:changed` or `pnpm check:changed` selects broad/shared lanes, it belongs in Testbox; do not let it continue locally after it fans out.
- Docs/changelog-only and CI/workflow metadata-only changes are not changed-gate work by default. Use `git diff --check` plus the relevant formatter/docs/workflow sanity check; escalate to `pnpm check:changed` only when scripts, test config, generated docs/API, package metadata, or runtime/build behavior changed.
- Rebase sanity: after a green `pnpm check:changed`, a clean rebase onto current
`origin/main` does not require rerunning the full changed gate when the rebase
has no conflicts and the branch diff is materially unchanged. Do a quick
- Vitest. Colocated `*.test.ts`; e2e `*.e2e.test.ts`; example models `sonnet-4.6`, `gpt-5.4`.
- Avoid brittle tests that grep workflow/docs strings for operator policy. Prefer executable behavior, parsed config/schema checks, or live run proof; put release/CI policy reminders in AGENTS/docs instead.
- Prefer injection; if module mocking, mock narrow local `*.runtime.ts`, not broad barrels or `openclaw/plugin-sdk/*`.
- Share fixtures/builders; delete duplicate assertions; assert behavior that can regress here.
- Do not edit baseline/inventory/ignore/snapshot/expected-failure files to silence checks without explicit approval.
- Do not run multiple independent `pnpm test`/Vitest commands concurrently in the same worktree. They can race on `node_modules/.experimental-vitest-cache` and fail with `ENOTEMPTY`. Use one grouped `pnpm test ...` invocation, run targeted lanes sequentially, or set distinct `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH` values when true parallel Vitest processes are needed.
- Test workers max 16. Memory pressure: `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
- Docs change with behavior/API. Use docs list/read_when hints; docs links per `docs/AGENTS.md`.
- Docs final answers: when doc files changed, end with the relevant full `https://docs.openclaw.ai/...` URL(s).
- Changelog user-facing only; pure test/internal usually no entry.
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @steipete`.
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @codex`, `Thanks @openclaw`, or `Thanks @steipete`.
- Changelog bullets are always single-line. No wrapping/continuation across multiple lines. Long entries stay on one long line so dedupe, PR-ref, and credit-audit tooling work and so the visual style stays uniform.
- Version bump touches: `package.json`, `apps/android/app/build.gradle.kts`, `apps/ios/version.json` + `pnpm ios:version:sync`, macOS `Info.plist`, `docs/install/updating.md`. Appcast only for Sparkle release.
- Mobile LAN pairing: plaintext `ws://` loopback-only. Private-network `ws://` needs `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`; Tailscale/public use `wss://` or tunnel.
- Memory wiki: keep prompt digest tiny. The prompt should only say the wiki exists, prefer `wiki_search` / `wiki_get`, start from `reports/person-agent-directory.md` for people routing, use search modes (`find-person`, `route-question`, `source-evidence`, `raw-claim`) when useful, and verify contact data before use.
- People wiki provenance: generated identity, social, contact, and "fun detail" notes need explicit source class/confidence (`maintainer-whois`, Discrawl sample/stat, GitHub profile, maintainer repo file). Do not promote inferred details to facts.
- Rebrand/migration/config warnings: run `openclaw doctor`.
- Never edit `node_modules`.
- Local-only `.agents` ignores: `.git/info/exclude`, not repo `.gitignore`.
@@ -64,6 +64,7 @@ These are frequently reported but are typically closed with no code change:
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
- Reports that only show untrusted media bytes reaching a maintained native decoder dependency (for example Sharp/libvips/libheif) without proving the shipped dependency version is vulnerable and demonstrating crash, memory corruption, data exposure, or a boundary bypass through OpenClaw. JavaScript header sniffing and image dimension fast-paths are preflight/UX checks, not the security boundary for native decoder correctness.
- Reports whose only impact is transient extra memory, CPU, or allocation work from decoding, base64 expansion, media transcoding, serialization, or other format conversion after the input was already accepted under OpenClaw's configured size/trust limits, including base64 decode-before-size-estimate findings. These are performance issues, not vulnerabilities, unless the report demonstrates unauthenticated amplification, bypass of configured limits, crash/process termination, persistent resource exhaustion, data exposure, or another documented boundary bypass.
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
@@ -75,6 +76,7 @@ These are frequently reported but are typically closed with no code change:
- Claims that Microsoft Teams `fileConsent/invoke``uploadInfo.uploadUrl` is attacker-controlled without demonstrating one of: auth boundary bypass, a real authenticated Teams/Bot Framework event carrying attacker-chosen URL, or compromise of the Microsoft/Bot trust path.
- Scanner-only claims against stale/nonexistent paths, or claims without a working repro.
- Reports that restate an already-fixed issue against later released versions without showing the vulnerable path still exists in the shipped tag or published artifact for that later version.
- SSRF reports against the operator-managed HTTP/WebSocket proxy-routing feature whose only claim is that ordinary process-local HTTP clients (`fetch`, `node:http`, `node:https`, WebSocket clients, axios/got/node-fetch-style clients) can reach an internal, metadata, private, or otherwise sensitive destination when proxy routing is disabled, missing, or the operator-managed proxy policy allows it. For this feature, OpenClaw provides fail-closed proxy routing when enabled; the external proxy's destination policy is operator infrastructure, not an OpenClaw-controlled security boundary. See [Network proxy](https://docs.openclaw.ai/security/network-proxy).
### Duplicate Report Handling
@@ -148,9 +150,11 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- Reports whose only claim is that an ACP-exposed tool can indirectly execute commands, mutate host state, or reach another privileged tool/runtime without demonstrating a bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. These are hardening-only findings, not vulnerabilities.
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
- Reports whose only claim is parser reachability in an up-to-date maintained dependency without showing that the exact shipped dependency build is vulnerable. We keep native media dependencies current; dependency exposure alone is not a vulnerability.
- Reports whose only claim is resource overhead from decode/encode, base64 expansion, media transcoding, serialization, or format-conversion order after input has already passed the applicable configured acceptance limits, including base64 decode-before-size-estimate findings. These are performance-only and should be ignored for GHSA triage unless the report demonstrates unauthenticated amplification, limit bypass, crash/process termination, persistent exhaustion, data exposure, or another documented boundary bypass.
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
- Reports whose only claim is that a platform-provided upload destination URL is untrusted (for example Microsoft Teams `fileConsent/invoke``uploadInfo.uploadUrl`) without proving attacker control in an authenticated production flow.
- SSRF reports limited to the operator-managed HTTP/WebSocket proxy-routing feature where the demonstrated mitigation is to enable/configure `proxy.enabled` with a filtering `proxy.proxyUrl`/`OPENCLAW_PROXY_URL`, or where impact depends on a permissive/misconfigured operator proxy. This only covers normal process-local HTTP(S)/WebSocket egress (`fetch`, Node HTTP(S), and similar JavaScript clients); non-HTTP egress and other features are assessed separately. See [Network proxy](https://docs.openclaw.ai/security/network-proxy).
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.