#20455: fix(msteams): prevent EADDRINUSE restart loop
channel: msteams
size: XS
Cluster:
Fix Microsoft Teams Plugin Issues
## Summary
- Fix MS Teams provider entering infinite EADDRINUSE restart loop after gateway startup
- Root cause: `monitorMSTeamsProvider` returned a synchronously resolved value, causing the subsystem manager to treat it as "provider exited" and trigger auto-restart while port 3978 was still bound
- Changed the return to a pending `Promise<MonitorMSTeamsResult>` that only resolves when the abort signal fires or the HTTP server closes, matching the lifecycle contract expected by the subsystem manager
- Also improved the server error handler to include the error code in the log message string, since the logger wrapper drops the metadata object
## Details
The subsystem manager wraps provider return values with `Promise.resolve(task)`. When the MS Teams provider returned `{ app, shutdown }` synchronously, this resolved immediately, triggering the restart logic while Express was still listening on port 3978. Subsequent restart attempts hit `EADDRINUSE`, and the health monitor eventually gave up after exhausting retry limits — leaving the MS Teams channel completely offline.
Other providers (e.g., Telegram) keep their promise pending via a polling loop, which is why only MS Teams is affected.
The bug has been present since the MS Teams plugin was introduced in `d9f9e93de` (Jan 16, 2026). Confirmed reproduced on `2026.2.17`.
### Behavior change
| Before | After |
|--------|-------|
| Provider returns resolved `{ app, shutdown }` | Provider returns pending `Promise` |
| Subsystem manager immediately schedules restart | Promise stays pending until abort/close |
| Port 3978 still bound → EADDRINUSE | Clean shutdown before promise resolves |
| Channel offline after ~10 restart attempts | Channel stays running indefinitely |
## Test plan
- [ ] Start gateway with MS Teams channel enabled
- [ ] Verify provider starts once and stays running (no `auto-restart` log messages)
- [ ] Verify `systemctl --user restart openclaw-gateway` cleanly restarts the provider (no EADDRINUSE)
- [ ] Verify sending/receiving messages works after the fix
- [ ] Verify abort signal still triggers clean shutdown
- [ ] Run existing test suite
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixed infinite EADDRINUSE restart loop caused by MS Teams provider returning a synchronously resolved promise. The subsystem manager wraps provider tasks with `Promise.resolve(task)` at `src/gateway/server-channels.ts:203`, treating immediate resolution as "provider exited" and triggering auto-restart while port 3978 remained bound.
**Key changes:**
- Changed return from synchronous `{ app, shutdown }` to pending `Promise<MonitorMSTeamsResult>`
- Promise now only resolves when abort signal fires or HTTP server closes, matching lifecycle contract
- Improved error logging to include error code inline (since logger drops metadata)
**How it works:**
The pending promise signals to the subsystem manager that the provider is still running. When the promise resolves (via abort or server close), the manager's `.finally()` block sets `running: false` and schedules a restart if needed. This matches the pattern used by Telegram provider's polling loop at `src/telegram/monitor.ts:174-213`.
<h3>Confidence Score: 4/5</h3>
- Safe to merge with minor style consideration
- The fix correctly addresses the root cause (synchronously resolved promise causing premature restart). The logic is sound and matches the subsystem manager's expectations. Deducted one point for having two resolve paths that could race, though this doesn't affect correctness since promises only resolve once. The error logging improvement is a nice addition.
- No files require special attention
<sub>Last reviewed commit: 2f78913</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#22605: fix(msteams): keep provider promise pending until abort to stop aut...
by OpakAlex · 2026-02-21
91.6%
#22182: fix(msteams): prevent false auto-restart loop after successful startup
by pandego · 2026-02-20
87.5%
#23226: fix(msteams): proactive messaging, EADDRINUSE fix, tool status, ada...
by TarogStar · 2026-02-22
79.7%
#21956: fix(line): block monitorLineProvider on abort signal to prevent cra...
by lailoo · 2026-02-20
79.7%
#22184: fix(msteams): resolve EADDRINUSE double-start by awaiting port binding
by harshang03 · 2026-02-20
78.4%
#23621: fix(LINE): keep startAccount promise alive to prevent auto-restart ...
by ttakanawa · 2026-02-22
78.1%
#13089: fix(msteams): alias team config under channel conversation IDs for ...
by BradGroux · 2026-02-10
76.2%
#23134: fix(gateway): skip auto-restart for webhook channels that resolve i...
by puneet1409 · 2026-02-22
74.8%
#12953: fix: defer gateway restart until all replies are sent
by zoskebutler · 2026-02-10
74.7%
#21790: fix(msteams): deliver thread replies via continueConversation to su...
by BinHPdev · 2026-02-20
74.6%