#22184: fix(msteams): resolve EADDRINUSE double-start by awaiting port binding
channel: msteams
size: S
Cluster:
Fix Microsoft Teams Plugin Issues
## Summary
Fixes #22169 — msteams provider starts twice on gateway boot, causing EADDRINUSE restart loop.
**Root cause**
`monitorMSTeamsProvider()` called `expressApp.listen(port)` and then immediately returned `{ app, shutdown }`, before the HTTP server had actually bound to the port. The channel manager in `server-channels.ts` wraps `startAccount` in a tracked promise; when that promise resolves, it marks the provider as `running: false` and triggers the auto-restart logic. With the old code resolving before the port was even bound, the first instance would succeed on port 3978 while a second instance was simultaneously started — which then failed with `EADDRINUSE`. The pattern cascaded through all 10 restart attempts and was then restarted by the health monitor, repeating indefinitely.
**Changes**
- **`extensions/msteams/src/monitor.ts`** — Core fix. `monitorMSTeamsProvider()` now:
1. Awaits the `'listening'` event on the `http.Server` before proceeding, so a bind failure surfaces as a rejected promise (rather than a silent early return that triggers a restart).
2. Stays alive by awaiting the abort signal, matching the lifecycle contract expected by `server-channels.ts`. The function only resolves after `shutdown()` completes, exactly like the Discord and Slack providers.
- The `MonitorMSTeamsResult` type and its early-return pattern are removed; the return type is now `Promise<void>`.
- An `onListening` hook (typed as `MSTeamsOnListeningCallback`) is added to `MonitorMSTeamsOpts` so callers can react to the moment the port is confirmed bound.
- **`extensions/msteams/src/channel.ts`** — `startAccount` now passes an `onListening` callback that calls `ctx.setStatus({ accountId, port })` only after the server is confirmed listening. Previously the port status was reported before the server was even started, creating a window where the channel appeared live but was not yet accepting connections.
- **`extensions/msteams/src/probe.ts`** — Adds `probeWebhookPort(port)`, a lightweight TCP connect that checks whether the webhook server is actually reachable on the local loopback. `probeMSTeams()` now runs this probe in parallel with the Bot Framework credential check and includes the result in `ProbeMSTeamsResult`, giving the health monitor a real liveness signal beyond credential validation.
- **`extensions/msteams/src/monitor-types.ts`** — Exports `MSTeamsOnListeningCallback` type.
- **`extensions/msteams/src/index.ts`** — Exports the new public surface: `MonitorMSTeamsOpts`, `MSTeamsOnListeningCallback`, `probeWebhookPort`, `ProbeMSTeamsResult`.
## Test plan
- [ ] Start gateway with msteams configured: `openclaw gateway start`
- [ ] Verify only one `msteams provider started on port 3978` log line appears
- [ ] Verify no `auto-restart attempt` log lines appear after a clean start
- [ ] Verify provider remains stable and processes incoming webhooks
- [ ] Stop gateway: `openclaw gateway stop` — confirm clean shutdown log appears
- [ ] Restart gateway — confirm single-start behaviour repeats
- [ ] Configure an invalid port (e.g. port 1 on Linux without root) — confirm the error is reported as a startup failure and the provider does not loop
Most Similar PRs
#20455: fix(msteams): prevent EADDRINUSE restart loop
by taradtke · 2026-02-18
78.4%
#22605: fix(msteams): keep provider promise pending until abort to stop aut...
by OpakAlex · 2026-02-21
77.1%
#22182: fix(msteams): prevent false auto-restart loop after successful startup
by pandego · 2026-02-20
75.4%
#20309: [BUG]: fix telegram webhook should wait for abort signal instead of...
by kesor · 2026-02-18
68.3%
#21956: fix(line): block monitorLineProvider on abort signal to prevent cra...
by lailoo · 2026-02-20
65.1%
#23226: fix(msteams): proactive messaging, EADDRINUSE fix, tool status, ada...
by TarogStar · 2026-02-22
64.8%
#23621: fix(LINE): keep startAccount promise alive to prevent auto-restart ...
by ttakanawa · 2026-02-22
64.7%
#16736: fix: stagger multi-account channel startup to avoid Discord rate li...
by rm289 · 2026-02-15
64.5%
#22694: telegram: stabilize multi-account webhook mode
by Dongik · 2026-02-21
64.4%
#13910: fix(discord): harden gateway reconnect recovery
by BYWallace · 2026-02-11
63.5%