-
-
Notifications
You must be signed in to change notification settings - Fork 69.7k
MS Teams provider: EADDRINUSE restart loop — missing await-until-abort in monitorMSTeamsProvider #25527
Description
Bug Description
The MS Teams channel provider enters an infinite auto-restart loop immediately after starting. The gateway interprets the provider as "stopped" within milliseconds of startup, triggers a restart, and the second bind attempt fails with EADDRINUSE on the webhook port (default 3978). This continues until MAX_RESTART_ATTEMPTS (10) is reached.
Versions affected: v2026.2.21-2, v2026.2.23 (likely all versions since the msteams extension was introduced)
Root Cause
The monitorMSTeamsProvider() function in extensions/msteams/src/monitor.ts returns immediately after expressApp.listen() binds to the port:
const httpServer = expressApp.listen(port, () => {
log.info(`msteams provider started on port ${port}`);
});
// ...
if (opts.abortSignal) {
opts.abortSignal.addEventListener("abort", () => {
void shutdown();
});
}
return { app: expressApp, shutdown }; // ← resolves immediately!The gateway's startChannelInternal() in gateway-cli-*.js treats the resolved startAccount promise as "provider stopped" and triggers auto-restart:
[default] auto-restart attempt 1/10 in 5s
EADDRINUSE: address already in use :::3978
[default] auto-restart attempt 2/10 in 11s
...
Compare with the Telegram webhook provider (src/telegram/webhook.ts), which correctly keeps the promise pending:
await new Promise((resolve) => server.listen(port, host, resolve));
// ...
if (opts.abortSignal && !opts.abortSignal.aborted) {
await new Promise<void>((resolve) => {
opts.abortSignal!.addEventListener("abort", () => resolve(), { once: true });
});
}The MS Teams provider is missing this "await until abort" block.
Fix
In extensions/msteams/src/monitor.ts, replace the abort handler and return at the end of monitorMSTeamsProvider():
- // Handle abort signal
- if (opts.abortSignal) {
- opts.abortSignal.addEventListener("abort", () => {
- void shutdown();
- });
- }
-
- return { app: expressApp, shutdown };
+ // Keep the provider alive until abort signal fires.
+ // Without this await, the startAccount promise resolves immediately after
+ // the HTTP server binds, causing the gateway to interpret it as "provider
+ // stopped" and triggering an auto-restart loop (EADDRINUSE on port reuse).
+ if (opts.abortSignal && !opts.abortSignal.aborted) {
+ await new Promise<void>((resolve) => {
+ opts.abortSignal!.addEventListener("abort", () => resolve(), { once: true });
+ });
+ }
+
+ await shutdown();
+ return { app: expressApp, shutdown };This matches the pattern used by the Telegram webhook provider and ensures the startAccount promise stays pending while the HTTP server is running.
Steps to Reproduce
- Configure MS Teams channel with valid Azure Bot credentials
- Start the gateway:
openclaw gateway --port 18790 - Observe logs: provider starts, immediately triggers restart loop
- After 10 attempts, gateway gives up
Environment
- OpenClaw v2026.2.23
- Node.js v22.22.0
- Linux (Ubuntu 24.04)
- Express 5.2.x (msteams extension dependency)
Related
- [voice-call] EADDRINUSE on gateway restart — no port conflict handling in webhook server #8926 (voice-call EADDRINUSE — same pattern, different channel)