Skip to content

fix(plugins): preserve external capability provider fallback#76536

Merged
steipete merged 2 commits into
openclaw:mainfrom
Conan-Scott:fix/capability-provider-cold-fallback
May 3, 2026
Merged

fix(plugins): preserve external capability provider fallback#76536
steipete merged 2 commits into
openclaw:mainfrom
Conan-Scott:fix/capability-provider-cold-fallback

Conversation

@Conan-Scott

Copy link
Copy Markdown
Contributor

Summary

  • Problem: after v2026.5.2, enabled external capability providers declared only through manifest contracts (for example contracts.speechProviders) can fail with no provider registered unless they are also startup-loaded.
  • Why it matters: docs describe manifest ownership/contract fallback as preserving correctness; missing startup activation should cost performance, not make an installed provider unusable.
  • What changed: preserve the v2026.5.2 fast path that reuses an already-loaded runtime registry, but fall back to the scoped manifest-derived runtime load when that registry is missing the provider.
  • What did NOT change (scope boundary): this does not change plugin activation policy, startup sidecar selection, bundled provider compatibility capture, or provider config semantics.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes N/A
  • Related N/A
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: commit 8283c5d6cc changed capability provider fallback loading from resolveRuntimePluginRegistry(params.loadOptions) to getLoadedRuntimePluginRegistry(...). That preserved startup-registry reuse, but dropped the cold-load path for enabled external providers that are declared in manifest contracts but absent from the startup registry.
  • Missing detection / guardrail: no regression test covered an enabled external manifest-contract provider that is not startup-loaded.
  • Contributing context (if known): Fish Audio @conan-scott/openclaw-fish-audio@1.1.0 declares contracts.speechProviders: ["fish-audio"]; without activation.onStartup, v2026.5.2 could discover the contract but failed to register the runtime provider on request.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/plugins/capability-provider-runtime.test.ts
  • Scenario the test should lock in: an enabled external speech provider declared via contracts.speechProviders is absent from the startup registry, then resolves through the scoped cold-load path.
  • Why this is the smallest reliable guardrail: the regression is in capability-provider runtime selection, before provider-specific TTS behavior matters.
  • Existing test that already covers this (if any): none found.
  • If no new test is added, why not: N/A; a regression test is added.

User-visible / Behavior Changes

Enabled external capability providers declared through manifest contracts can again resolve on request without requiring activation.onStartup for correctness.

Diagram (if applicable)

Before:
TTS request -> manifest contract identifies external provider -> startup registry missing provider -> no provider registered

After:
TTS request -> manifest contract identifies external provider -> startup registry missing provider -> scoped cold load -> provider registered

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux container / OpenShift pod (linux 6.12.0-153.el10.x86_64, Node 24.14.0)
  • Runtime/container: OpenClaw 2026.5.2
  • Model/provider: N/A
  • Integration/channel (if any): TTS via Fish Audio speech provider
  • Relevant config (redacted): Fish Audio installed and enabled; installed manifest declares contracts.speechProviders: ["fish-audio"]; activation.onStartup removed for the repro.

Steps

  1. Install/enable @conan-scott/openclaw-fish-audio@1.1.0 with contracts.speechProviders: ["fish-audio"] and no activation.onStartup.
  2. Hard-restart the OpenClaw pod on unpatched 2026.5.2.
  3. Run a TTS request using Fish Audio.
  4. Apply this PR's runtime change as a loader monkey patch only; keep the Fish plugin manifest unpatched.
  5. Hard-restart the pod again.
  6. Run the same TTS request.

Expected

  • The enabled provider declared by contracts.speechProviders should be request-loadable and usable.

Actual

  • Before patch: TTS failed with fish-audio: no provider registered; microsoft: not configured; minimax: not configured.
  • After patch: the same Fish Audio TTS path worked after hard pod restart with no plugin-side activation patch.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Local automated checks:

  • pnpm exec vitest run src/plugins/capability-provider-runtime.test.ts — 37 passed
  • pnpm exec oxfmt --check src/plugins/capability-provider-runtime.ts src/plugins/capability-provider-runtime.test.ts — passed
  • pnpm exec oxlint src/plugins/capability-provider-runtime.ts src/plugins/capability-provider-runtime.test.ts — 0 warnings / 0 errors

Manual runtime evidence:

  • Baseline hard restart, Fish manifest without activation.onStartup, no runtime patch: fish-audio: no provider registered; microsoft: not configured; minimax: not configured.
  • Monkey-patched runtime with this PR's code, Fish manifest still without activation.onStartup, hard restarted pod: /tts audio exact reinstall hard restart test after monkey patch worked.

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: baseline failure on unpatched 2026.5.2; success after applying only this runtime fallback as a loader monkey patch; hard pod restart on both sides.
  • Edge cases checked: Fish plugin manifest intentionally left without activation.onStartup during the passing test, so success came from runtime fallback rather than plugin startup activation.
  • What you did not verify: a full packaged OpenClaw image built from this branch; broader non-speech providers beyond the unit coverage.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: reintroducing cold provider loads could partially reduce the startup-registry reuse optimization from v2026.5.2.
    • Mitigation: the cold load only runs when no compatible loaded registry is available for the scoped provider request; the fast path remains first.

@clawsweeper

clawsweeper Bot commented May 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR restores scoped cold-loading for enabled external manifest-contract capability providers, adds a Fish Audio regression test, and records the fix in the changelog.

Reproducibility: yes. Source inspection on current main shows manifest-contract ids can be selected while the provider-entry loader only reads a loaded runtime registry, and the PR body provides a concrete Fish Audio before/after runtime reproduction.

Next step before merge
No repair job is needed; the branch already contains the narrow code, test, and changelog change, so the next action is CI and maintainer merge review.

Security
Cleared: The diff changes plugin runtime lookup logic plus a unit test and changelog only; it adds no dependency, workflow, secret, permission, or package-resolution surface.

Review details

Best possible solution:

Merge the scoped fallback and regression test once exact-head CI is green, while tracking the related path-based plugin tools work separately under #76598/#76609.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection on current main shows manifest-contract ids can be selected while the provider-entry loader only reads a loaded runtime registry, and the PR body provides a concrete Fish Audio before/after runtime reproduction.

Is this the best way to solve the issue?

Yes. The PR restores the previous scoped cold-load behavior only after a loaded-registry miss, preserves the startup-registry fast path, and adds focused regression coverage for the external manifest-contract case.

Acceptance criteria:

  • pnpm test src/plugins/capability-provider-runtime.test.ts
  • pnpm exec oxfmt --check --threads=1 src/plugins/capability-provider-runtime.ts src/plugins/capability-provider-runtime.test.ts
  • pnpm exec oxlint src/plugins/capability-provider-runtime.ts src/plugins/capability-provider-runtime.test.ts
  • pnpm check:changed

What I checked:

  • current-main-missing-fallback: On current main, loadCapabilityProviderEntries() only consults getLoadedRuntimePluginRegistry() and returns the loaded entries unchanged when there are no bundled compatibility plugin ids, so an enabled external provider missing from the startup registry can resolve to no entries. (src/plugins/capability-provider-runtime.ts:417, 2b82c05a7fb1)
  • loaded-registry-miss-path: getLoadedRuntimePluginRegistry() returns undefined when the active compatible registry does not contain the required scoped plugin ids, which matches a manifest-declared external provider that was not startup-loaded. (src/plugins/active-runtime-registry.ts:84, 2b82c05a7fb1)
  • documented-correctness-contract: The manifest docs say missing non-startup activation metadata should usually cost performance, not correctness, while manifest ownership fallbacks still exist. Public docs: docs/plugins/manifest.md. (docs/plugins/manifest.md:393, 2b82c05a7fb1)
  • pr-fallback-implementation: The PR imports resolveRuntimePluginRegistry() and calls it with the scoped load options only after the loaded-registry lookup misses, preserving the fast path while restoring cold loading. (src/plugins/capability-provider-runtime.ts:421, ea6e57f6bcd0)
  • pr-regression-test: The PR adds a regression test where an enabled global Fish Audio plugin declares contracts.speechProviders, is absent from the startup registry, and resolves through an activate:false scoped runtime load. (src/plugins/capability-provider-runtime.test.ts:472, ea6e57f6bcd0)
  • exact-head-ci-state: At review time, the exact PR head had successful security/lint/type/build-smoke checks, with several broader checks still in progress and no actionable failure tied to the touched files. (ea6e57f6bcd0)

Likely related people:

  • DmitryPogodaev: The regression appears tied to the startup runtime registry reuse commit, which changed helper paths to prefer loaded registries and touched the capability-provider and active-runtime registry area. (role: introduced recent behavior; confidence: high; commits: 8283c5d6cc3f; files: src/plugins/capability-provider-runtime.ts, src/plugins/active-runtime-registry.ts)
  • shakkernerd: Recent manifest-contract and capability-provider preservation work in the same area touched capability-provider runtime tests and manifest contract behavior. (role: recent adjacent owner; confidence: medium; commits: dfde770a3aed, 1de7362679be, e6825fceaa04; files: src/plugins/capability-provider-runtime.ts, src/plugins/capability-provider-runtime.test.ts, src/plugins/manifest-contract-eligibility.ts)
  • steipete: The shared active runtime lookup helper was introduced by a recent maintainer refactor and is central to the loaded-registry path this PR adjusts. (role: recent maintainer; confidence: medium; commits: 848348f423b5; files: src/plugins/active-runtime-registry.ts)

Remaining risk / open question:

  • Exact-head CI still had in-progress checks at review time, so merge should wait for required checks to finish or be proven unrelated.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 2b82c05a7fb1.

@Conan-Scott Conan-Scott force-pushed the fix/capability-provider-cold-fallback branch from b0cf1f1 to ce1d827 Compare May 3, 2026 08:49
@steipete steipete force-pushed the fix/capability-provider-cold-fallback branch 5 times, most recently from 5557cfd to 4e0332f Compare May 3, 2026 12:15
@steipete steipete force-pushed the fix/capability-provider-cold-fallback branch from 4e0332f to ea6e57f Compare May 3, 2026 12:18
@steipete steipete merged commit 8ebf86c into openclaw:main May 3, 2026
100 checks passed
@Conan-Scott Conan-Scott deleted the fix/capability-provider-cold-fallback branch May 3, 2026 13:05
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…w#76536)

* fix(plugins): preserve external capability provider fallback

* docs: move changelog entry to avoid merge conflict

---------

Co-authored-by: Clawdbot <clawdbot@apilab.us>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…w#76536)

* fix(plugins): preserve external capability provider fallback

* docs: move changelog entry to avoid merge conflict

---------

Co-authored-by: Clawdbot <clawdbot@apilab.us>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…w#76536)

* fix(plugins): preserve external capability provider fallback

* docs: move changelog entry to avoid merge conflict

---------

Co-authored-by: Clawdbot <clawdbot@apilab.us>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…w#76536)

* fix(plugins): preserve external capability provider fallback

* docs: move changelog entry to avoid merge conflict

---------

Co-authored-by: Clawdbot <clawdbot@apilab.us>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…w#76536)

* fix(plugins): preserve external capability provider fallback

* docs: move changelog entry to avoid merge conflict

---------

Co-authored-by: Clawdbot <clawdbot@apilab.us>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants