Skip to content

bug(plugin-sdk): normalizeDiagnosticEventsModule hardcodes stale minified export alias #87082

Description

@Kaspre

Summary

src/plugin-sdk/root-alias.cjs:normalizeDiagnosticEventsModule hardcodes mod.r as the fallback alias for onDiagnosticEvent when the named export isn't found on the loaded diagnostic-events chunk. As of 2026.5.25-beta.1, the bundler assigns r to emitFailoverEvent and u to onDiagnosticEvent, so the fallback maps the wrong function.

This causes TypeError: unsubscribeDiagnosticEvents is not a function on every gateway stop/restart for any plugin that subscribes to diagnostic events via the SDK wrapper.

Root cause

// src/plugin-sdk/root-alias.cjs
function normalizeDiagnosticEventsModule(mod) {
  if (typeof mod.onDiagnosticEvent === "function") return mod;  // named export — works
  if (typeof mod.r === "function") {                            // ← assumes r = onDiagnosticEvent
    return { ...mod, onDiagnosticEvent: mod.r };                // ← actually emitFailoverEvent on 5.25
  }
  return mod;
}

The function correctly checks for the named export first, but the single-letter fallback mod.r is fragile — the bundler reassigns export aliases across builds.

Impact

  1. onDiagnosticEvent(listener) in the SDK wrapper calls emitFailoverEvent(listener) instead, which returns a non-function value.
  2. The shared-state fallback path in onDiagnosticEvent() (lines 164-168) correctly subscribes the listener via onDiagnosticEventFromSharedState, so data collection works.
  3. The combo unsubscribe closure at line 166 calls unsubscribeDiagnosticEvents() on the non-function return value → TypeError.
  4. Every plugin that uses onDiagnosticEvent from the SDK gets this error on stop.

Suggested fix

Replace the hardcoded mod.r check with Function.name introspection:

function normalizeDiagnosticEventsModule(mod) {
  if (!mod || typeof mod !== "object") return mod;
  if (typeof mod.onDiagnosticEvent === "function") return mod;
  const fn = Object.values(mod).find(
    (v) => typeof v === "function" && v.name === "onDiagnosticEvent",
  );
  if (fn) return { ...mod, onDiagnosticEvent: fn };
  return mod;
}

JavaScript functions retain their original .name property regardless of export aliasing, so this works across all builds without tracking which letter the bundler assigns.

Reproduction

Any plugin that calls onDiagnosticEvent from openclaw/plugin-sdk on OpenClaw 2026.5.25-beta.1 will see TypeError: unsubscribeDiagnosticEvents is not a function in the gateway journal on every stop/restart.

Environment

  • OpenClaw 2026.5.25-beta.1
  • Gateway with otel-observability plugin (henrikrexed/openclaw-observability-plugin)
  • WSL2, Node v26.1.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions