Skip to content

MS Teams provider: EADDRINUSE restart loop — missing await-until-abort in monitorMSTeamsProvider #25527

@BennyWindolph

Description

@BennyWindolph

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

  1. Configure MS Teams channel with valid Azure Bot credentials
  2. Start the gateway: openclaw gateway --port 18790
  3. Observe logs: provider starts, immediately triggers restart loop
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions