openclaw - 💡(How to fix) Fix Bundled extensions use async register() but loader silently drops the returned promise [4 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#62125Fetched 2026-04-08 03:08:40
View on GitHub
Comments
4
Participants
1
Timeline
7
Reactions
0
Participants
Timeline (top)
commented ×4closed ×1mentioned ×1subscribed ×1

In 2026.4.4 (and likely earlier), seven bundled extensions ship with async register() implementations that perform await import("./register.runtime.js") (or similar) before doing the real work. The plugin loader, however, calls register(api) synchronously and only emits a warning diagnostic if the returned value is thenable — it does not await the promise. The async registration work is therefore enqueued on the microtask queue and runs after the loader has already moved on, opening a startup race window where providers, services, and hooks may not be registered when other code expects them to be.

openclaw doctor makes this directly visible:

- WARN acpx: plugin register returned a promise; async registration is ignored
- WARN amazon-bedrock: plugin register returned a promise; async registration is ignored
- WARN anthropic: plugin register returned a promise; async registration is ignored
- WARN github-copilot: plugin register returned a promise; async registration is ignored
- WARN openai: plugin register returned a promise; async registration is ignored
- WARN openrouter: plugin register returned a promise; async registration is ignored
- WARN vllm: plugin register returned a promise; async registration is ignored

These are bundled extensions shipping inside the openclaw package itself, so users cannot fix this by updating a third-party plugin.

Error Message

  • WARN acpx: plugin register returned a promise; async registration is ignored
  • WARN amazon-bedrock: plugin register returned a promise; async registration is ignored
  • WARN anthropic: plugin register returned a promise; async registration is ignored
  • WARN github-copilot: plugin register returned a promise; async registration is ignored
  • WARN openai: plugin register returned a promise; async registration is ignored
  • WARN openrouter: plugin register returned a promise; async registration is ignored
  • WARN vllm: plugin register returned a promise; async registration is ignored
  1. Observe the WARN entries above under "Plugin diagnostics". level: "warn", Change dist/loader-*.js (source: somewhere under src/plugins/loader.ts based on the symbol names) so it actually awaits async registration. The PR queue already has the relevant change in #34195 — that PR proposed treating async register as a load error, but it was auto-closed by the openclaw-barnacle bot for the author's PR-queue limit, not on merit. A simpler variant is to just await result (with a timeout) when it's a promise, instead of warning.

Root Cause

The loader at dist/loader-*.js (search for the literal "plugin register returned a promise; async registration is ignored") does:

try {
    const result = register(api);
    if (result && typeof result.then === "function")
        registry.diagnostics.push({
            level: "warn",
            pluginId: record.id,
            source: record.source,
            message: "plugin register returned a promise; async registration is ignored"
        });
    // ... no await; loader continues
    registry.plugins.push(record);
}

Each affected bundled extension then defines register like this — example from dist/extensions/anthropic/index.js:

var anthropic_default = definePluginEntry({
    id: "anthropic",
    name: "Anthropic Provider",
    description: "Bundled Anthropic provider plugin",
    async register(api) {
        const { registerAnthropicPlugin } = await import("./register.runtime.js");
        await registerAnthropicPlugin(api);
    }
});

The same async register() + await import("./register.runtime.js") pattern is present in:

  • dist/extensions/anthropic/index.js
  • dist/extensions/amazon-bedrock/index.js
  • dist/extensions/openai/index.js (multiple awaited dynamic imports)
  • dist/extensions/openrouter/index.js
  • dist/extensions/vllm/index.js
  • dist/extensions/github-copilot/index.js
  • dist/extensions/acpx/index.js

Because the loader does not await register(), the dynamic-import + registerXxxPlugin(api) chain runs on the microtask queue after registry.plugins.push(record). Anything that enumerates the registry between those two moments sees an incomplete plugin.

Fix Action

Fix / Workaround

  • Race condition during gateway startup: providers/services/hooks may be missing in the window between "loader marks plugin as loaded" and "the dropped promise resolves". Length of the window depends on how long the dynamic imports take (cold cache, slow disk → larger window).
  • Plausibly causes intermittent startup flakiness. I observed Telegram channel hanging at "starting provider" with no follow-up after the upgrade to 2026.4.4 — the channel reported "failed (unknown) - This operation was aborted" via doctor until a manual systemctl restart, after which it came up cleanly. That symptom is consistent with another bundled plugin not being ready when the channel tried to register against it. (See also #56492 for a Discord variant of the same race.)
  • Cosmetic but persistent doctor noise — 7 WARNs every run, indistinguishable from third-party-plugin bugs.

Code Example

- WARN acpx: plugin register returned a promise; async registration is ignored
- WARN amazon-bedrock: plugin register returned a promise; async registration is ignored
- WARN anthropic: plugin register returned a promise; async registration is ignored
- WARN github-copilot: plugin register returned a promise; async registration is ignored
- WARN openai: plugin register returned a promise; async registration is ignored
- WARN openrouter: plugin register returned a promise; async registration is ignored
- WARN vllm: plugin register returned a promise; async registration is ignored

---

try {
    const result = register(api);
    if (result && typeof result.then === "function")
        registry.diagnostics.push({
            level: "warn",
            pluginId: record.id,
            source: record.source,
            message: "plugin register returned a promise; async registration is ignored"
        });
    // ... no await; loader continues
    registry.plugins.push(record);
}

---

var anthropic_default = definePluginEntry({
    id: "anthropic",
    name: "Anthropic Provider",
    description: "Bundled Anthropic provider plugin",
    async register(api) {
        const { registerAnthropicPlugin } = await import("./register.runtime.js");
        await registerAnthropicPlugin(api);
    }
});

---

const result = register(api);
if (result && typeof result.then === "function") {
    await result; // or: await Promise.race([result, timeout])
}

---

import { registerAnthropicPlugin } from "./register.runtime.js";

var anthropic_default = definePluginEntry({
    id: "anthropic",
    name: "Anthropic Provider",
    description: "Bundled Anthropic provider plugin",
    register(api) { registerAnthropicPlugin(api); }
});
RAW_BUFFERClick to expand / collapse

Summary

In 2026.4.4 (and likely earlier), seven bundled extensions ship with async register() implementations that perform await import("./register.runtime.js") (or similar) before doing the real work. The plugin loader, however, calls register(api) synchronously and only emits a warning diagnostic if the returned value is thenable — it does not await the promise. The async registration work is therefore enqueued on the microtask queue and runs after the loader has already moved on, opening a startup race window where providers, services, and hooks may not be registered when other code expects them to be.

openclaw doctor makes this directly visible:

- WARN acpx: plugin register returned a promise; async registration is ignored
- WARN amazon-bedrock: plugin register returned a promise; async registration is ignored
- WARN anthropic: plugin register returned a promise; async registration is ignored
- WARN github-copilot: plugin register returned a promise; async registration is ignored
- WARN openai: plugin register returned a promise; async registration is ignored
- WARN openrouter: plugin register returned a promise; async registration is ignored
- WARN vllm: plugin register returned a promise; async registration is ignored

These are bundled extensions shipping inside the openclaw package itself, so users cannot fix this by updating a third-party plugin.

Repro

  1. Install/run openclaw 2026.4.4 (verified against the npm tarball / nix-openclaw 0f4d0666).
  2. Run openclaw doctor.
  3. Observe the WARN entries above under "Plugin diagnostics".

Root cause

The loader at dist/loader-*.js (search for the literal "plugin register returned a promise; async registration is ignored") does:

try {
    const result = register(api);
    if (result && typeof result.then === "function")
        registry.diagnostics.push({
            level: "warn",
            pluginId: record.id,
            source: record.source,
            message: "plugin register returned a promise; async registration is ignored"
        });
    // ... no await; loader continues
    registry.plugins.push(record);
}

Each affected bundled extension then defines register like this — example from dist/extensions/anthropic/index.js:

var anthropic_default = definePluginEntry({
    id: "anthropic",
    name: "Anthropic Provider",
    description: "Bundled Anthropic provider plugin",
    async register(api) {
        const { registerAnthropicPlugin } = await import("./register.runtime.js");
        await registerAnthropicPlugin(api);
    }
});

The same async register() + await import("./register.runtime.js") pattern is present in:

  • dist/extensions/anthropic/index.js
  • dist/extensions/amazon-bedrock/index.js
  • dist/extensions/openai/index.js (multiple awaited dynamic imports)
  • dist/extensions/openrouter/index.js
  • dist/extensions/vllm/index.js
  • dist/extensions/github-copilot/index.js
  • dist/extensions/acpx/index.js

Because the loader does not await register(), the dynamic-import + registerXxxPlugin(api) chain runs on the microtask queue after registry.plugins.push(record). Anything that enumerates the registry between those two moments sees an incomplete plugin.

Impact

  • Race condition during gateway startup: providers/services/hooks may be missing in the window between "loader marks plugin as loaded" and "the dropped promise resolves". Length of the window depends on how long the dynamic imports take (cold cache, slow disk → larger window).
  • Plausibly causes intermittent startup flakiness. I observed Telegram channel hanging at "starting provider" with no follow-up after the upgrade to 2026.4.4 — the channel reported "failed (unknown) - This operation was aborted" via doctor until a manual systemctl restart, after which it came up cleanly. That symptom is consistent with another bundled plugin not being ready when the channel tried to register against it. (See also #56492 for a Discord variant of the same race.)
  • Cosmetic but persistent doctor noise — 7 WARNs every run, indistinguishable from third-party-plugin bugs.

Suggested fixes

Two independent changes — both are cheap and either would close the bug, but doing both is the right call:

1. Make the loader await register()

Change dist/loader-*.js (source: somewhere under src/plugins/loader.ts based on the symbol names) so it actually awaits async registration. The PR queue already has the relevant change in #34195 — that PR proposed treating async register as a load error, but it was auto-closed by the openclaw-barnacle bot for the author's PR-queue limit, not on merit. A simpler variant is to just await result (with a timeout) when it's a promise, instead of warning.

Pseudocode:

const result = register(api);
if (result && typeof result.then === "function") {
    await result; // or: await Promise.race([result, timeout])
}

This is fully backwards-compatible — sync plugins are unchanged.

2. Make the bundled extensions register synchronously

The await import("./register.runtime.js") pattern in each bundled extension exists to keep the cold-start cost of unused providers off the critical path. That's still achievable without async register: replace the dynamic import with a top-level static import, since these files all live in the same bundle and Rollup will tree-shake unused branches anyway.

Example for anthropic/index.js:

import { registerAnthropicPlugin } from "./register.runtime.js";

var anthropic_default = definePluginEntry({
    id: "anthropic",
    name: "Anthropic Provider",
    description: "Bundled Anthropic provider plugin",
    register(api) { registerAnthropicPlugin(api); }
});

If the lazy-load is genuinely load-bearing for some of these (e.g. openai/index.js pulls in 4+ submodules), then Fix #1 (await in the loader) is the cleaner option for those.

Related

  • #34195 — PR proposing exactly this loader hardening; auto-closed by bot for queue limit, never reviewed on merit. Worth reopening.
  • #56492 — Open issue documenting the same fire-and-forget bug class for the Discord/Carbon Client constructor and SafeGatewayPlugin.registerClient. Confirms the maintainers are aware that fire-and-forget async registration is a real production problem.
  • #56519 — Closed PR proposing a Discord-specific fix for #56492.

Environment

  • openclaw 2026.4.4 (nix-openclaw 0f4d0666, Apr 4)
  • node 22.22.0
  • linux 6.12.78

extent analysis

TL;DR

The most likely fix is to make the loader await register() to ensure asynchronous registrations are completed before proceeding.

Guidance

  1. Await register() in the loader: Modify dist/loader-*.js to await the register() function if it returns a promise, ensuring that asynchronous registrations are completed before the loader continues.
  2. Verify the fix: Run openclaw doctor after applying the fix to confirm that the WARN entries for async registration are resolved.
  3. Consider synchronous registration for bundled extensions: As an alternative or additional fix, modify the bundled extensions to register synchronously by replacing dynamic imports with top-level static imports.
  4. Review related issues: Examine related issues like #34195, #56492, and #56519 to ensure that similar problems are addressed.

Example

const result = register(api);
if (result && typeof result.then === "function") {
    await result; // or: await Promise.race([result, timeout])
}

Notes

The provided pseudocode assumes that register() returns a promise. If register() may return other thenable values, additional checks may be necessary.

Recommendation

Apply the workaround by awaiting register() in the loader, as this fix is backwards-compatible and addresses the root cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING