Skip to content

Bundled channel plugins cause stack overflow via jiti module loading #61259

@fjventura20

Description

@fjventura20

Summary

All bundled channel plugins fail to load with RangeError: Maximum call stack size exceeded during gateway startup. The stack overflow occurs in jiti's recursive module evaluator (eval_evalModulejitiRequire chain), which creates a deeply nested call stack when loading compiled dist chunks. By the time downstream dependencies (ajv, highlight.js, etc.) initialize, the call stack is exhausted.

Workaround: Set OPENCLAW_SKIP_CHANNEL_PLUGINS=1 environment variable to bypass channel plugin loading entirely. This has been added to start-openclaw.ps1 as a temporary measure.

Root Cause Analysis

The Loading Chain

Each channel extension loads through jiti via dist-runtime/extensions/<channel>/index.jsdist/extensions/<channel>/index.js, which triggers a deep dependency chain:

channel extension → channel-core → extension-shared → runtime → command-secret-gateway → call → method-scopes → (ajv/highlight.js)

Each jiti step adds multiple frames (jitiRequireeval_evalModule → anonymous wrapper). By the time the chain reaches modules that do significant initialization work (ajv schema compilation, highlight.js loading), the stack is exhausted.

Stack Trace

[openclaw] Failed to start CLI: RangeError: Maximum call stack size exceeded
    at schemaHasRulesForType (node_modules/ajv/dist/compile/validate/applicability.js:6:39)
    ...
    at method-scopes-XXXX.js:1884:35  (ajv.compile at module level)
    at ModuleJobSync.runSync (node:internal/modules/esm/module_job)
    at loadESMFromCJS (node:internal/modules/cjs/loader)
    at jitiRequire → eval_evalModule (repeated ~7 levels deep)
    at dist/extensions/googlechat/index.js

Contributing Factors

  1. ~70+ ajv.compile() calls at module level in gateway/protocol/index.ts — each adds recursive stack frames for schema walking
  2. jiti's recursive CJS↔ESM bridge — each module in the chain adds 2-3 stack frames
  3. Transitive dependency depth — channel extensions pull in the entire gateway protocol module through shared chunks

Changes Already Applied

1. Lazy ajv compilation (src/gateway/protocol/index.ts)

Replaced ~70 eager ajv.compile() calls with lazyCompile() wrappers that defer compilation to first use. This prevents ajv from consuming stack during module initialization.

2. Strip $schema from bundled schemas (src/plugins/bundled-plugin-metadata.ts)

The runtime metadata loader was passing schemas with $schema: "http://json-schema.org/draft-07/schema#" to ajv, causing it to try compiling the self-referential JSON Schema meta-schema. Now stripped at collection time.

3. validateSchema: false on all ajv instances

Added to both schema-validator.ts and gateway/protocol/index.ts to prevent ajv from validating schemas against the meta-schema.

4. Regenerated bundled-channel-config-metadata.generated.ts

The $schema property is now stripped by stripSchemaProperty() in the generation script.

5. OPENCLAW_SKIP_CHANNEL_PLUGINS env var bypass (src/channels/plugins/bundled.ts)

When set, skips loadGeneratedBundledChannelEntries() entirely, returning an empty array.

What Still Needs to Be Done

  • Investigate why jiti creates such deep stacks loading compiled dist files (these are already JS — should they even need jiti?)
  • Consider using native ESM import() for bundled channel loading instead of jiti's CJS bridge
  • Evaluate if dist-runtime proxy files can use dynamic import() instead of re-exports
  • Profile the actual stack depth during channel loading to determine minimum --stack-size needed
  • Remove the OPENCLAW_SKIP_CHANNEL_PLUGINS workaround once the underlying jiti issue is resolved

Files Modified

  • src/gateway/protocol/index.ts — lazy ajv compilation
  • src/plugins/schema-validator.tsvalidateSchema: false
  • src/plugins/bundled-plugin-metadata.ts — strip $schema from runtime schemas
  • src/channels/plugins/bundled.tsOPENCLAW_SKIP_CHANNEL_PLUGINS bypass
  • scripts/generate-bundled-channel-config-metadata.tsstripSchemaProperty() helper
  • start-openclaw.ps1 — set OPENCLAW_SKIP_CHANNEL_PLUGINS=1
  • src/config/bundled-channel-config-metadata.generated.ts — regenerated without $schema

Environment

  • Node.js v22.18.0
  • Windows 11 Pro
  • Default stack size (~1MB)

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