Skip to content

Telegram: fix webhook multi-account routing (respect bindings.accountId)#821

Merged
steipete merged 3 commits intoopenclaw:mainfrom
gumadeiras:fix-bindings-telegram-webhook
Jan 13, 2026
Merged

Telegram: fix webhook multi-account routing (respect bindings.accountId)#821
steipete merged 3 commits intoopenclaw:mainfrom
gumadeiras:fix-bindings-telegram-webhook

Conversation

@gumadeiras
Copy link
Copy Markdown
Member

Fixes #803

This PR fixes a routing bug where Telegram inbound messages received via webhook mode were always routed as accountId=default, causing bindings like { provider: "telegram", accountId: "opie" } to never match and messages to fall back to the default agent (main). The webhook server now passes the configured accountId (and loaded config) into createTelegramBot(), so resolveAgentRoute() evaluates bindings correctly and sessions no longer leak across agents.

What changed

  • webhook.ts: startTelegramWebhook() now accepts accountId + config and forwards both into createTelegramBot().
  • monitor.ts: passes accountId + config into startTelegramWebhook() when webhook mode is enabled.
  • webhook.test.ts: regression test to ensure webhook startup forwards accountId into createTelegramBot().
  • bot.test.ts: regression test that bindings.match.accountId routes a DM to the bound agent session.

Reproduction
Config:

{
  telegram: {
    enabled: true,
    accounts: {
      steve: { botToken: "...", dmPolicy: "pairing" },
      opie:  { botToken: "...", dmPolicy: "pairing", webhookUrl: "https://example.com/telegram-webhook" }
    }
  },
  bindings: [
    { agentId: "opie", match: { provider: "telegram", accountId: "opie" } }
  ]
}

Before:

  • DM to opie bot routes to agent:main:... (binding doesn’t match because webhook bot runs as accountId=default).

After:

  • DM to opie bot routes to agent:opie:main and payload carries AccountId: "opie".

Validation
Tested locally with three bots and works as intended.
Sample config:

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-opus-4-5",
        "fallbacks": [
          "google-antigravity/claude-opus-4-5-thinking",
          "anthropic/claude-sonnet-4-5",
          "google-antigravity/gemini-3-pro-high",
          "google-antigravity/gemini-3-flash"
        ]
      },
      "models": {
        "google-antigravity/claude-opus-4-5-thinking": {},
        "anthropic/claude-opus-4-5": {
          "alias": "opus"
        },
        "anthropic/claude-sonnet-4-5": {
          "alias": "sonnet"
        },
        "google-antigravity/gemini-3-flash": {
          "alias": "gemini-3-flash"
        },
        "google-antigravity/gemini-3-pro-high": {
          "alias": "gemini-3-pro"
        },
        "anthropic/claude-haiku-4-5": {
          "alias": "haiku"
        }
      },
      "workspace": "/Users/gumadeiras/clawd",
      "contextPruning": {
        "mode": "adaptive"
      },
      "heartbeat": {
        "every": "60m",
        "model": "google-antigravity/gemini-3-pro-high",
        "target": "last"
      }
    },
    "list": [
      {
        "id": "pinguini",
        "default": true,
        "workspace": "/Users/gumadeiras/clawd-pinguini",
        "model": "anthropic/claude-opus-4-5",
        "subagents": {
          "allowAgents": [
            "*"
          ]
        }
      },
      {
        "id": "poe",
        "workspace": "/Users/gumadeiras/clawd-poe",
        "model": "google-antigravity/gemini-3-pro-high",
        "tools": {
          "deny": [
            "peekaboo"
          ]
        }
      },
      {
        "id": "flurry",
        "workspace": "/Users/username/clawd-flurry",
        "model": "google-antigravity/gemini-3-flash",
        "tools": {
          "deny": [
            "peekaboo"
          ]
        }
      }
    ]
  },
  "bindings": [
    {
      "agentId": "pinguini",
      "match": {
        "provider": "telegram",
        "accountId": "pinguini"
      }
    },
    {
      "agentId": "poe",
      "match": {
        "provider": "telegram",
        "accountId": "poe"
      }
    },
    {
      "agentId": "flurry",
      "match": {
        "provider": "telegram",
        "accountId": "flurry"
      }
    }
  ],
  "telegram": {
    "capabilities": [
      "inlineButtons"
    ],
    "enabled": true,
    "dmPolicy": "pairing",
    "groupPolicy": "open",
    "streamMode": "off",
    "accounts": {
      "pinguini": {
        "dmPolicy": "allowlist",
        "botToken": "82...s",
        "allowFrom": [
          "0123456789"
        ],
        "groupPolicy": "allowlist",
        "streamMode": "partial"
      },
      "poe": {
        "dmPolicy": "allowlist",
        "botToken": "85...k",
        "allowFrom": [
          "0123456789"
        ],
        "groupPolicy": "allowlist",
        "streamMode": "partial"
      },
      "flurry": {
        "dmPolicy": "allowlist",
        "botToken": "85...M",
        "allowFrom": [
          "0123456789"
        ],
        "groupPolicy": "allowlist",
        "streamMode": "partial"
      }
    }
  }
}

Notes / follow-ups

  • MS Teams appears to have a similar limitation: inbound routing currently calls resolveAgentRoute() without an accountId, so account-scoped bindings can’t work there until a stable account identifier is defined for Teams.

Copilot AI review requested due to automatic review settings January 13, 2026 03:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a routing bug where Telegram webhook messages were always routed with accountId=default, preventing account-specific bindings from matching correctly. The fix ensures that the accountId and config are passed through the webhook initialization flow to createTelegramBot(), enabling proper multi-account routing.

Changes:

  • Added accountId and config parameters to webhook initialization flow
  • Updated webhook and bot tests to verify correct accountId routing behavior
  • Maintained backward compatibility by making new parameters optional

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/telegram/webhook.ts Added accountId and config optional parameters to startTelegramWebhook() and passed them to createTelegramBot()
src/telegram/monitor.ts Updated webhook initialization to pass accountId and config from the resolved account
src/telegram/webhook.test.ts Added test verification that accountId is forwarded to createTelegramBot()
src/telegram/bot.test.ts Added comprehensive test verifying that DMs are correctly routed by accountId binding to the appropriate agent session

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +41 to +43
expect(createTelegramBotSpy).toHaveBeenCalledWith(
expect.objectContaining({ accountId: "opie" }),
);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verifies that accountId is passed to createTelegramBot, but doesn't verify that config is also passed. Consider also checking for the config parameter in the assertion to ensure both new parameters are properly forwarded.

Copilot uses AI. Check for mistakes.
@steipete steipete self-assigned this Jan 13, 2026
gumadeiras and others added 3 commits January 13, 2026 03:34
The Telegram webhook and monitor now accept and pass through accountId and config parameters, enabling routing and configuration per Telegram account. Tests have been updated to verify correct bot instantiation and DM routing based on accountId bindings.
Refactors tests to use a shared config object and adds stricter assertions to verify that the config is passed correctly to createTelegramBot. Ensures the bindings property is checked in the test expectations.
@steipete steipete force-pushed the fix-bindings-telegram-webhook branch from 641a55a to bab7eea Compare January 13, 2026 03:40
@steipete steipete merged commit cddd836 into openclaw:main Jan 13, 2026
11 of 21 checks passed
@steipete
Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm lint && pnpm build && pnpm test
  • Land commit: bab7eea
  • Merge commit: cddd836

Thanks @gumadeiras!

victorarias pushed a commit to victorarias/clawdbot that referenced this pull request Jan 13, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…-webhook

Telegram: fix webhook multi-account routing (respect bindings.accountId)
Fato07 pushed a commit to Fato07/elav that referenced this pull request Mar 26, 2026
…me-instance-cleanup

WIP: Simplify plugin runtime and cleanup lifecycle
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram multi-account bindings not routing to correct agent

3 participants