Skip to content

fix(msteams): keep startAccount Promise pending while server runs#24391

Closed
justinhuangcode wants to merge 3 commits intoopenclaw:mainfrom
justinhuangcode:fix/msteams-restart-loop
Closed

fix(msteams): keep startAccount Promise pending while server runs#24391
justinhuangcode wants to merge 3 commits intoopenclaw:mainfrom
justinhuangcode:fix/msteams-restart-loop

Conversation

@justinhuangcode
Copy link
Copy Markdown
Contributor

@justinhuangcode justinhuangcode commented Feb 23, 2026

Summary

  • monitorMSTeamsProvider() returned immediately after starting the Express server, causing the channel orchestrator to treat it as "account exited" and schedule a restart — which led to an infinite restart loop with EADDRINUSE errors
  • Wraps the server lifecycle in a long-lived Promise that stays pending while the HTTP server is running, rejects on server error, and resolves only on server close (graceful shutdown)
  • Handles abort signal for clean teardown, including the already-aborted edge case

This matches the pattern used by the Feishu channel plugin (extensions/feishu/src/monitor.ts).

Root Cause

The channel orchestrator calls startAccount() which returns a Promise from monitorMSTeamsProvider(). The old code called expressApp.listen() and then immediately returned { app, shutdown }, resolving the Promise. The orchestrator interprets a resolved startAccount Promise as "the account has exited" and schedules a restart. Since the port is still bound from the previous server instance, the restart fails with EADDRINUSE, creating an infinite loop.

Test plan

  • CI passes (oxfmt, oxlint, tsgo, vitest)
  • Manual verification: MS Teams provider starts once and stays running without restart loop
  • Abort signal triggers clean shutdown (server closes, Promise resolves)
  • Server error rejects the Promise (propagates to orchestrator for proper error handling)

Fixes #24374

🤖 Generated with Claude Code

Greptile Summary

Fixes the MS Teams channel infinite restart loop (#24374) by wrapping the Express server lifecycle in a long-lived Promise. The old code resolved the startAccount Promise immediately after calling expressApp.listen(), causing the channel orchestrator to interpret this as "account exited" and schedule a restart — while the port was still bound, leading to EADDRINUSE errors in a loop.

The new code:

  • Keeps the Promise pending while the HTTP server runs
  • Rejects on server error (propagating to the orchestrator for proper backoff-based restart)
  • Resolves only on server close (graceful shutdown via abort signal)
  • Handles the already-aborted edge case and uses { once: true } on the abort listener

This aligns with the pattern used by the Feishu channel plugin (extensions/feishu/src/monitor.ts).

Confidence Score: 4/5

  • This PR is safe to merge — it correctly fixes a critical infinite restart loop with a well-understood pattern.
  • The fix is straightforward and follows the same long-lived Promise pattern used by the Feishu channel plugin. The code correctly handles the three key scenarios: normal operation (Promise stays pending), server error (reject), and graceful shutdown via abort signal (close → resolve). The only minor imprecision (both reject and resolve being called on EADDRINUSE) is harmless due to Promise settlement semantics. No new functionality is introduced, and the change is scoped to a single file.
  • No files require special attention — the single changed file is well-structured and follows established patterns.

Last reviewed commit: 43bbc83

justinhuangcode and others added 3 commits February 25, 2026 04:45
monitorMSTeamsProvider() resolved immediately after starting the Express
server, which the channel orchestrator interpreted as "account exited".
This triggered an infinite restart loop with EADDRINUSE errors because
the port was still bound from the previous instance.

Wrap the server lifecycle in a long-lived Promise that:
- stays pending while the HTTP server is running
- rejects on server error (propagates to orchestrator)
- resolves only on server close (graceful shutdown)
- handles abort signal for clean teardown

This matches the pattern used by the Feishu channel plugin.

Fixes openclaw#24374

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Verify that monitorMSTeamsProvider:
- returns early when msteams is disabled or credentials are missing
- stays pending while the HTTP server is running
- resolves with app and shutdown when the server is closed via AbortSignal

Regression test for openclaw#24374 (infinite restart loop).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The monitor function reads core.channel.text.resolveTextChunkLimit
after credentials are resolved.  The stub was missing this property,
causing a TypeError on Windows CI where module resolution order
differs from Node on Linux.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@justinhuangcode justinhuangcode force-pushed the fix/msteams-restart-loop branch from 8e74691 to 8c2b1db Compare February 25, 2026 04:46
@justinhuangcode
Copy link
Copy Markdown
Contributor Author

Closing in favour of the consolidated fix thread on #24522.

The root cause is identical — monitorMSTeamsProvider() resolves immediately after expressApp.listen(), the channel orchestrator interprets this as "account exited", and the subsequent restart hits EADDRINUSE. Both #24580 and #25582 address the same issue there.

Happy to help review or iterate on the remaining approaches if useful.

@justinhuangcode justinhuangcode deleted the fix/msteams-restart-loop branch February 25, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: [msteams] startAccount resolves immediately causing restart loop and EADDRINUSE crash

1 participant