openclaw - ✅(Solved) Fix fix(startup): circular dep between telegram contract and config-runtime crashes gateway [1 pull requests, 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#60685Fetched 2026-04-08 02:48:21
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

After updating to 2026.4.3, the gateway fails to start with:

Error: telegram command config contract surface is unavailable
    at loadTelegramCommandConfigContract (dist/zod-schema.providers-core-BhgCFixp.js:88:23)

Error Message

Error: telegram command config contract surface is unavailable at loadTelegramCommandConfigContract (dist/zod-schema.providers-core-BhgCFixp.js:88:23)

Root Cause

src/config/telegram-command-config.ts (introduced in commit bc457fd1b8) calls loadTelegramCommandConfigContract() at module load time (top-level):

export const TELEGRAM_COMMAND_NAME_PATTERN =
  loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN;

This creates a circular dependency at startup:

dist/extensions/telegram/contract-api.jssecurity-audit-CfOZrG7S.jsconfig-runtime-DGvaAYOq.jszod-schema.providers-core-BhgCFixp.js (imports this at top-level) → loadTelegramCommandConfigContract() (called at module load time) → getBundledChannelContractSurfaceModule({ pluginId: "telegram" }) (tries to load telegram contract) → dist/extensions/telegram/contract-api.js already loading — returns null from jiti → throws "telegram command config contract surface is unavailable"

Fix Action

Fix

Do not call loadTelegramCommandConfigContract() at module load time. Either:

  1. Inline the hardcoded pattern /^[a-z0-9_]{1,32}$/ (it matches the extension source exactly) for TELEGRAM_COMMAND_NAME_PATTERN
  2. Or use lazy initialization so the contract is only loaded on first function call

The functions normalizeTelegramCommandName, normalizeTelegramCommandDescription, and resolveTelegramCustomCommands are fine since they're called at runtime, not at module load time.

PR fix notes

PR #60760: fix(telegram): break circular dependency that crashes gateway on startup

Description (problem / solution / changelog)

Summary

  • Problem: Since commit bc457fd1b8 (present on main as of 2026.4.3+), the gateway fails to start with Error: telegram command config contract surface is unavailable thrown at src/plugin-sdk/telegram-command-config.ts:35. The crash is deterministic and affects every startup when the Telegram extension is bundled.

  • Root Cause: src/plugin-sdk/telegram-command-config.ts line 40-41 assigns TELEGRAM_COMMAND_NAME_PATTERN as a module-level constant by calling loadTelegramCommandConfigContract() at module load time. This function calls getBundledChannelContractSurfaceModule({ pluginId: "telegram" }), which uses jiti to load extensions/telegram/contract-api.ts. However, contract-api.ts re-exports collectTelegramSecurityAuditFindings from ./src/security-audit.js, which imports openclaw/plugin-sdk/config-runtime. That module in turn re-exports from ./telegram-command-config.js — the very module that is still initializing. jiti detects the circular reference and returns null for the still-loading module, causing the contract variable to be null and the throw new Error(...) to fire. The three exported functions (normalizeTelegramCommandName, normalizeTelegramCommandDescription, resolveTelegramCustomCommands) do not trigger this issue because they call loadTelegramCommandConfigContract() lazily inside their function bodies, only at runtime when actually invoked.

  • Fix: Replace the module-level loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN call with an inlined regex literal /^[a-z0-9_]{1,32}$/. This value is identical to the canonical definition in extensions/telegram/src/command-config.ts line 1 and matches the Telegram Bot API specification for command names. By inlining the constant, the module no longer performs any side-effect calls at load time, completely eliminating the circular dependency trigger. A regression test is added to contract-surfaces.test.ts that asserts the inlined pattern stays in sync with the extension source.

  • What changed:

    • src/plugin-sdk/telegram-command-config.ts: Replaced loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN (module-level call) with an inlined regex literal and a JSDoc comment explaining the rationale.
    • src/channels/plugins/contract-surfaces.test.ts: Added a regression test that dynamically imports the inlined constant and compares its source and flags against the Telegram extension's contract surface to catch future drift.
  • What did NOT change (scope boundary):

    • extensions/telegram/src/command-config.ts — the canonical source of truth for the regex is untouched.
    • extensions/telegram/contract-api.ts and extensions/telegram/contract-surfaces.ts — no changes to extension public surfaces.
    • src/channels/plugins/contract-surfaces.ts — the contract surface loader is untouched.
    • src/plugin-sdk/config-runtime.ts — the re-export barrel is untouched; it still re-exports TELEGRAM_COMMAND_NAME_PATTERN from telegram-command-config.js.
    • src/config/zod-schema.providers-core.ts — the Zod schema that uses normalizeTelegramCommandName and resolveTelegramCustomCommands is untouched; those functions remain lazy and are unaffected.
    • No global module loading behavior, jiti configuration, or plugin discovery logic was modified.

Reproduction

  1. Check out the main branch at or after commit bc457fd1b8.
  2. Run pnpm build (or start the gateway directly if using a pre-built artifact).
  3. Observe the gateway crashes immediately on startup with:
    Error: telegram command config contract surface is unavailable
        at loadTelegramCommandConfigContract (dist/zod-schema.providers-core-BhgCFixp.js:88:23)
  4. The circular dependency chain is:
    extensions/telegram/contract-api.ts
      → ./src/security-audit.js
        → openclaw/plugin-sdk/config-runtime
          → ./telegram-command-config.js
            → loadTelegramCommandConfigContract() [module load time]
              → getBundledChannelContractSurfaceModule({ pluginId: "telegram" })
                → jiti loads extensions/telegram/contract-api.ts [already loading → null]
                  → throws "telegram command config contract surface is unavailable"

Risk / Mitigation

  • Risk: The inlined regex could drift from the canonical definition in extensions/telegram/src/command-config.ts if someone updates the extension pattern without updating the inlined copy.
  • Mitigation: The new regression test in contract-surfaces.test.ts dynamically loads both the inlined constant and the extension contract surface, then asserts their source and flags are identical. Any drift will cause a test failure in the contracts test lane (pnpm test:contracts). Additionally, the JSDoc comment on the inlined constant explicitly references the extension source file and line number, making the relationship discoverable during code review.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway
  • Telegram
  • Plugin SDK
  • Config

Linked Issue/PR

Fixes #60685

Changed files

  • src/channels/plugins/contract-surfaces.test.ts (modified, +18/-0)
  • src/plugin-sdk/telegram-command-config.ts (modified, +17/-2)

Code Example

Error: telegram command config contract surface is unavailable
    at loadTelegramCommandConfigContract (dist/zod-schema.providers-core-BhgCFixp.js:88:23)

---

export const TELEGRAM_COMMAND_NAME_PATTERN =
  loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN;
RAW_BUFFERClick to expand / collapse

Summary

After updating to 2026.4.3, the gateway fails to start with:

Error: telegram command config contract surface is unavailable
    at loadTelegramCommandConfigContract (dist/zod-schema.providers-core-BhgCFixp.js:88:23)

Root cause

src/config/telegram-command-config.ts (introduced in commit bc457fd1b8) calls loadTelegramCommandConfigContract() at module load time (top-level):

export const TELEGRAM_COMMAND_NAME_PATTERN =
  loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN;

This creates a circular dependency at startup:

dist/extensions/telegram/contract-api.jssecurity-audit-CfOZrG7S.jsconfig-runtime-DGvaAYOq.jszod-schema.providers-core-BhgCFixp.js (imports this at top-level) → loadTelegramCommandConfigContract() (called at module load time) → getBundledChannelContractSurfaceModule({ pluginId: "telegram" }) (tries to load telegram contract) → dist/extensions/telegram/contract-api.js already loading — returns null from jiti → throws "telegram command config contract surface is unavailable"

Fix

Do not call loadTelegramCommandConfigContract() at module load time. Either:

  1. Inline the hardcoded pattern /^[a-z0-9_]{1,32}$/ (it matches the extension source exactly) for TELEGRAM_COMMAND_NAME_PATTERN
  2. Or use lazy initialization so the contract is only loaded on first function call

The functions normalizeTelegramCommandName, normalizeTelegramCommandDescription, and resolveTelegramCustomCommands are fine since they're called at runtime, not at module load time.

extent analysis

TL;DR

To fix the gateway startup issue, avoid calling loadTelegramCommandConfigContract() at module load time by either inlining the hardcoded pattern or using lazy initialization.

Guidance

  • Identify and refactor any top-level code that calls loadTelegramCommandConfigContract() to use lazy initialization instead.
  • Consider inlining the hardcoded pattern /^[a-z0-9_]{1,32}$/ for TELEGRAM_COMMAND_NAME_PATTERN as a temporary workaround.
  • Review the code for any other potential circular dependencies that could cause similar issues.
  • Verify that the normalizeTelegramCommandName, normalizeTelegramCommandDescription, and resolveTelegramCustomCommands functions are not called at module load time.

Example

// Before
export const TELEGRAM_COMMAND_NAME_PATTERN =
  loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN;

// After (lazy initialization)
let telegramCommandNamePattern: RegExp;
export function getTelegramCommandNamePattern() {
  if (!telegramCommandNamePattern) {
    telegramCommandNamePattern = loadTelegramCommandConfigContract().TELEGRAM_COMMAND_NAME_PATTERN;
  }
  return telegramCommandNamePattern;
}

Notes

This fix assumes that the loadTelegramCommandConfigContract() function is not necessary at module load time and can be safely deferred until runtime.

Recommendation

Apply workaround by using lazy initialization to avoid calling loadTelegramCommandConfigContract() at module load time, as this approach allows for a more flexible and maintainable solution.

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

openclaw - ✅(Solved) Fix fix(startup): circular dep between telegram contract and config-runtime crashes gateway [1 pull requests, 1 participants]