Skip to content

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

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

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions