Skip to content

[Bug]: 2026.4.21 CLI startup regressed 8–12× — jiti.normalizeAliases O(n²) called fresh per plugin (no cross-call cache) #70186

@dpakman

Description

@dpakman

Summary

After upgrading to 2026.4.21, openclaw status, openclaw plugins list, and other CLI commands that touch the plugin loader regressed from ~instant to 12–20s wall time on a machine with 65/99 plugins loaded. CPU profile shows normalizeAliases in node_modules/jiti/dist/jiti.cjs consuming ~85% of runtime (10.3s of 12.2s total).

Environment

  • openclaw 2026.4.21 (f788c88)
  • macOS 26.4.1 arm64, node 25.8.1
  • 65/99 plugins loaded (most are bundled providers)

Repro

time openclaw gateway status > /dev/null
# 12.1s total, 18.5s user CPU

Root cause

src/plugins/jiti-loader-cache.ts memoizes the per-plugin aliasMap object (via cachedPluginSdkScopedAliasMaps), but the loader still calls createJiti(filename, { alias: aliasMap, ... }) per plugin. Inside Jiti, normalizeAliases(alias) is O(n²) over the alias keys and is NOT cached across calls (only self-memoizes via a hidden symbol on its own return object, which openclaw never feeds back in). With 65 plugins × O(n²) over the SDK subpath alias map, this dominates startup.

Top of CPU profile (status command):

 self_ms    %    function
  10351  84.5%  normalizeAliases  jiti.cjs
    273   2.2%  createJiti        jiti.cjs

Related: 2026.4.20 release notes mention "reuse plugin loader alias and Jiti config resolution across repeated same-context loads" — but the fix only covered the openclaw-side aliasMap cache, not Jiti's per-createJiti normalization cost.

Workaround / fix

Content-/identity-keyed cache around normalizeAliases. Local WeakMap patch (5-line diff in jiti.cjs) drops status time from 12.1s → 1.6s (≈7× faster). Prototype diff:

var __jitiNAWM = globalThis.__jitiNAWM ||
                 (globalThis.__jitiNAWM = new WeakMap());
function normalizeAliases(e) {
  if (e[pt]) return e;
  var c = __jitiNAWM.get(e); if (c) return c;
  // ... existing body ...
  __jitiNAWM.set(e, t);
  return Object.defineProperty(t, pt, {value:!0,enumerable:!1}), t;
}

Better fix lives in openclaw: wrap createJiti in getCachedPluginJitiLoader so identical (filename, aliasMap, tryNative) triples share the Jiti instance cross-plugin, or pass the normalized aliasMap in and have Jiti short-circuit via its existing pt marker.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions