openclaw - 💡(How to fix) Fix [Bug]: Telegram `nativeAliases` consume separate `/`-menu slots

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…

When a defineChatCommand({ ... }) entry declares nativeAliases, every alias is pushed to Telegram as a separate menu entry, even though all aliases route to the same handler via textAliases. As a result, a single registry entry with N aliases consumes N + 1 slots of the 100-command Telegram cap. This is the proximate cause of the chronic [telegram] limits bots to 100 commands. 101 configured log warning on installs with the default skill loadout: the 101st slot is /side, an alias of /btw that doesn't need to be in the menu.

There is no user-config escape hatch for this — the channels.telegram.commands schema is additionalProperties: false and exposes only native: bool|"auto" and nativeSkills: bool|"auto". Operators can disable the whole menu, or disable all skill commands, but cannot suppress an individual alias.

Root Cause

  • ~/.openclaw/openclaw.json has channels.telegram.commands: {} (defaults), and 56 ready skills.
  • Total canonical chat-command entries (built-in + skill-derived) come in at exactly 100 distinct entries. Adding the single alias /side (declared as a nativeAlias on the /btw entry — see registry snippet below) pushes the count to 101.
  • On gateway restart, stderr emits:
    [telegram] limits bots to 100 commands. 101 configured; registering first 100.
    Use channels.telegram.commands.native: false to disable, or reduce
    plugin/skill/custom commands.
    [telegram] menu text exceeded the conservative 5700-character payload budget;
    shortening descriptions to keep 100 commands visible.
  • Telegram's getMyCommands API returns 100 commands. Empirically the 101st is /side (the alias that lost the registration race against the 100th canonical command). /btw itself remains menu-visible.
  • Result: /side works as a typed command (because textAliases routes it), but does NOT appear in the bot's / autocomplete or menu. Two-tier UX divergence for what is logically one feature.

Fix Action

Fix / Workaround

  • Any registry entry with nativeAliases: [...] consumes one menu slot per alias, regardless of whether the alias adds discoverability the canonical command doesn't already cover.

  • Skill-derived commands tend to introduce aliases (/office + /office_word + /office_excel style families, where the family head is sometimes an alias of a canonical command), so as the skill catalogue grows the slot pressure compounds.

  • The current 100-command cap is a hard Telegram-side limit (https://core.telegram.org/bots/api#botcommand); local workarounds beyond removing menu entries do not exist.

  • Stderr warnings catalog (this install's housekeeping decision log): https://github.com/openclaw/openclaw (not yet a public reference — internal doc on the reporting operator's machine; happy to share more excerpts on request).

  • The same install also has a separate, unrelated open issue around user-plugin hook handlers not dispatching at runtime: https://github.com/openclaw/openclaw/issues/85174.

Code Example

[telegram] limits bots to 100 commands. 101 configured; registering first 100.
  Use channels.telegram.commands.native: false to disable, or reduce
  plugin/skill/custom commands.
  [telegram] menu text exceeded the conservative 5700-character payload budget;
  shortening descriptions to keep 100 commands visible.

---

# 1. Confirm the same registry entry handles both /btw and /side
grep -nA8 '"btw"' /opt/homebrew/lib/node_modules/openclaw/dist/commands-registry.data-*.js
# Expected to print something like:
#   key: "btw",
#   nativeName: "btw",
#   nativeAliases: ["side"],
#   textAliases: ["/btw", "/side"],

# 2. Restart gateway and observe the warning
launchctl kickstart -k gui/501/ai.openclaw.gateway
sleep 12
grep -E "limits bots|menu text exceeded" ~/Library/Logs/openclaw/gateway.err.log | tail -3

# 3. Query TG for the registered menu
TOKEN=$(security find-generic-password -s openclaw -a telegram-botToken -w)
curl -s "https://api.telegram.org/bot${TOKEN}/getMyCommands" | python3 -m json.tool | head -200
# Confirm /btw is present, /side is absent (or vice-versa, depending on which
# alias of /btw lost the race for the 100th slot).

# 4. Confirm the schema gap
openclaw config schema | python3 -c "
import json, sys
s = json.load(sys.stdin)
print(json.dumps(s['properties']['channels']['properties']['telegram']['properties']['commands'], indent=2))
"
# Expected: only 'native' and 'nativeSkills' (with `additionalProperties: false`).
# No per-command suppress allowed.

---

defineChatCommand({
  key: "btw",
  nativeName: "btw",
  nativeAliases: ["side"],
  description: "Ask a side question without changing future session context.",
  textAliases: ["/btw", "/side"],
  acceptsArgs: true,
  category: "tools",
  tier: "standard"
}),
RAW_BUFFERClick to expand / collapse

Summary

When a defineChatCommand({ ... }) entry declares nativeAliases, every alias is pushed to Telegram as a separate menu entry, even though all aliases route to the same handler via textAliases. As a result, a single registry entry with N aliases consumes N + 1 slots of the 100-command Telegram cap. This is the proximate cause of the chronic [telegram] limits bots to 100 commands. 101 configured log warning on installs with the default skill loadout: the 101st slot is /side, an alias of /btw that doesn't need to be in the menu.

There is no user-config escape hatch for this — the channels.telegram.commands schema is additionalProperties: false and exposes only native: bool|"auto" and nativeSkills: bool|"auto". Operators can disable the whole menu, or disable all skill commands, but cannot suppress an individual alias.

Expected behavior

nativeAliases should either:

  1. (Preferred) Not be pushed to the Telegram menu at all. textAliases already routes /side to the /btw handler — that path doesn't require the alias to be menu-visible. The native menu should advertise the canonical command only, and the menu-slot budget should be measured against canonical commands.
  2. (Acceptable alternative) Be pushed only when there is budget left after all canonical commands are placed. So a 100-canonical install never has aliases crowding out a real command; a 60-canonical install could include all aliases.
  3. (Operator-flexible alternative) Expose a config knob like channels.telegram.commands.nativeAliases: bool|"auto" (default false or "auto" meaning "if budget allows"), so operators on the edge of the 100 cap can opt out of alias menu entries without losing canonical menu visibility.

Actual behavior

Reproduced on 2026.5.19 (a185ca2), npm-installed (/opt/homebrew/lib/node_modules/openclaw/openclaw.mjs):

  • ~/.openclaw/openclaw.json has channels.telegram.commands: {} (defaults), and 56 ready skills.
  • Total canonical chat-command entries (built-in + skill-derived) come in at exactly 100 distinct entries. Adding the single alias /side (declared as a nativeAlias on the /btw entry — see registry snippet below) pushes the count to 101.
  • On gateway restart, stderr emits:
    [telegram] limits bots to 100 commands. 101 configured; registering first 100.
    Use channels.telegram.commands.native: false to disable, or reduce
    plugin/skill/custom commands.
    [telegram] menu text exceeded the conservative 5700-character payload budget;
    shortening descriptions to keep 100 commands visible.
  • Telegram's getMyCommands API returns 100 commands. Empirically the 101st is /side (the alias that lost the registration race against the 100th canonical command). /btw itself remains menu-visible.
  • Result: /side works as a typed command (because textAliases routes it), but does NOT appear in the bot's / autocomplete or menu. Two-tier UX divergence for what is logically one feature.

Reproduction recipe

# 1. Confirm the same registry entry handles both /btw and /side
grep -nA8 '"btw"' /opt/homebrew/lib/node_modules/openclaw/dist/commands-registry.data-*.js
# Expected to print something like:
#   key: "btw",
#   nativeName: "btw",
#   nativeAliases: ["side"],
#   textAliases: ["/btw", "/side"],

# 2. Restart gateway and observe the warning
launchctl kickstart -k gui/501/ai.openclaw.gateway
sleep 12
grep -E "limits bots|menu text exceeded" ~/Library/Logs/openclaw/gateway.err.log | tail -3

# 3. Query TG for the registered menu
TOKEN=$(security find-generic-password -s openclaw -a telegram-botToken -w)
curl -s "https://api.telegram.org/bot${TOKEN}/getMyCommands" | python3 -m json.tool | head -200
# Confirm /btw is present, /side is absent (or vice-versa, depending on which
# alias of /btw lost the race for the 100th slot).

# 4. Confirm the schema gap
openclaw config schema | python3 -c "
import json, sys
s = json.load(sys.stdin)
print(json.dumps(s['properties']['channels']['properties']['telegram']['properties']['commands'], indent=2))
"
# Expected: only 'native' and 'nativeSkills' (with `additionalProperties: false`).
# No per-command suppress allowed.

Registry entry referenced

From dist/commands-registry.data-DSe2RN0_.js (2026.5.19, lines ~280-289 on this build — line numbers may drift across versions, but the entry shape is stable):

defineChatCommand({
  key: "btw",
  nativeName: "btw",
  nativeAliases: ["side"],
  description: "Ask a side question without changing future session context.",
  textAliases: ["/btw", "/side"],
  acceptsArgs: true,
  category: "tools",
  tier: "standard"
}),

textAliases already routes both /btw and /side to the same handler. The nativeAliases: ["side"] declaration is the only thing causing /side to also claim a menu slot.

Why this matters more than "just /btw"

On this install /btw//side is the cleanest example, but the pattern generalizes:

  • Any registry entry with nativeAliases: [...] consumes one menu slot per alias, regardless of whether the alias adds discoverability the canonical command doesn't already cover.
  • Skill-derived commands tend to introduce aliases (/office + /office_word + /office_excel style families, where the family head is sometimes an alias of a canonical command), so as the skill catalogue grows the slot pressure compounds.
  • The current 100-command cap is a hard Telegram-side limit (https://core.telegram.org/bots/api#botcommand); local workarounds beyond removing menu entries do not exist.

Operator surface today

Available config switches (channels.telegram.commands):

  • native: bool | "auto" — switch off the entire / autocomplete menu.
  • nativeSkills: bool | "auto" — switch off all skill commands in the menu.

Neither is finer-grained than that, so operators bumping into the cap must either hide all / autocomplete or hide all skill commands. There is no way to drop a single alias.

Proposed fix

Cheapest change, behavior-only: in the code path that builds the Telegram setMyCommands payload from the registry, skip entries that are aliases (detect via entry.key !== alias_name). This collapses the 101→100 case without schema or surface changes. Existing textAliases routing still makes the dropped aliases callable by typing.

If that breaks discoverability for someone who genuinely wants aliases visible: gate the new behavior behind a channels.telegram.commands.nativeAliases: bool|"auto" knob (default "auto" = drop aliases only when over budget; true = always include; false = never include).

Verification after fix

Pre-fix:

  • [telegram] limits bots to 100 commands. 101 configured in stderr at every restart that resyncs the menu.
  • getMyCommands returns 100 commands with /btw present, /side absent (or the other-alias absent).

Post-fix (option 1, no schema change):

  • No limits bots warning at restart.
  • getMyCommands returns 100 (or fewer) canonical commands. No aliases in the menu. Typing /side still works because textAliases routes it.

Post-fix (option 3, with knob):

  • Default "auto": identical to option 1 behavior whenever total > 100. Below 100, aliases included.
  • Explicit false: behaves like option 1 unconditionally.

Environment

  • OpenClaw: 2026.5.19 (a185ca2), npm-installed (brew tap openclaw/tap formula exists but is inactive).
  • Node: bundled in npm-global install path.
  • OS: macOS Darwin 24.6.0 (Sequoia 15.x).
  • Channel: Telegram, polling ingress (no webhook).
  • Skills: 56 ready of 98 installed (default catalogue on a recent fresh setup).

Related

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…

FAQ

Expected behavior

nativeAliases should either:

  1. (Preferred) Not be pushed to the Telegram menu at all. textAliases already routes /side to the /btw handler — that path doesn't require the alias to be menu-visible. The native menu should advertise the canonical command only, and the menu-slot budget should be measured against canonical commands.
  2. (Acceptable alternative) Be pushed only when there is budget left after all canonical commands are placed. So a 100-canonical install never has aliases crowding out a real command; a 60-canonical install could include all aliases.
  3. (Operator-flexible alternative) Expose a config knob like channels.telegram.commands.nativeAliases: bool|"auto" (default false or "auto" meaning "if budget allows"), so operators on the edge of the 100 cap can opt out of alias menu entries without losing canonical menu visibility.

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 - 💡(How to fix) Fix [Bug]: Telegram `nativeAliases` consume separate `/`-menu slots