Skip to content

fix(bot): supervise Feishu WebSocket connection to recover from silent SDK failures#3350

Open
WnareaJunior wants to merge 2 commits into
volcengine:mainfrom
WnareaJunior:fix/feishu-ws-silent-disconnect
Open

fix(bot): supervise Feishu WebSocket connection to recover from silent SDK failures#3350
WnareaJunior wants to merge 2 commits into
volcengine:mainfrom
WnareaJunior:fix/feishu-ws-silent-disconnect

Conversation

@WnareaJunior

Copy link
Copy Markdown

Summary

Primary affected domain: Integration / Bot Runtime (bot/vikingbot) — cc @yeshion23333

The Feishu bot stops responding after running for a while and only recovers on process restart. Root cause: the lark-oapi ws client's blocking start() never returns after startup — all receive/reconnect work runs in orphaned asyncio tasks whose failures are never observed. Four SDK defects (verified in lark-oapi 1.5.3 source, lark_oapi/ws/client.py) can leave the connection permanently dead while start() keeps sleeping:

  1. Reconnect budget exhaustion raises ServerUnreachableException inside an orphaned task — reconnection silently stops forever.
  2. ClientException during reconnect (transient auth failure, exceed-connection-limit) is re-raised by _try_connect — same silent task death.
  3. _connect() returns while holding its internal lock when a connection already exists (early return before the try/finally) — later sends/ACKs/disconnects deadlock.
  4. _get_conn_url uses sync requests.post with no timeout inside the event loop — a hang freezes the SDK's whole loop.

In all four cases the old run_ws retry loop in feishu.py never fires because start() neither returns nor raises, so the bot looks alive but is deaf.

This PR replaces the fire-and-forget ws thread with supervised connection generations:

  • Each generation runs a fresh lark.ws.Client on its own event loop, so a poisoned client (dead reconnect task, leaked lock, wedged loop) can always be replaced with a clean one.
  • A watchdog on the channel loop checks connection health every 15s, gives the SDK's own auto-reconnect a 60s grace period, then neutralizes the dead client (disables its reconnect, closes its connection bypassing the leak-prone internal lock, stops its loop so the blocked start() unwinds) and rebuilds a clean generation — even when the old thread is stuck in a blocking call (it is abandoned with a warning).
  • Health transitions are logged (connection is downRestarting Feishu WebSocket client: <reason>connection recovered), and messages arriving with no running channel loop are no longer dropped silently.
  • stop() performs a real teardown instead of calling a close() method the SDK client does not have.

Type of Change

  • Bug fix (fix)

Testing

  • Unit tests pass
  • Manual testing completed

New regression suite bot/tests/test_feishu_ws_lifecycle.py (11 tests) uses fake ws clients so it runs with or without lark-oapi installed. It covers: watchdog rebuilds a dead-connection client and neutralizes the old generation; healthy connections are never churned; start() crashes are retried until success; a dead ws thread is respawned; stop() terminates thread and watchdog cleanly; a handler exception does not break later messages; duplicate deliveries are deduped.

Verified against the real SDK (lark-oapi 1.5.3): with invalid credentials the thread cycles real ClientException failures, the watchdog's forced rebuild fires, and shutdown is clean. To reproduce recovery end-to-end: run a connected bot, cut its network to Feishu for 5+ minutes (long enough to exhaust the SDK's reconnect budget), restore it — the bot recovers within ~60–75s without a restart, with the full log sequence above.

ruff check passes on both files.

Related Issues

Checklist

  • Code follows project style guidelines
  • Tests added for new functionality
  • Documentation updated (if needed)
  • All tests pass

🤖 Generated with Claude Code

Wilson Narea and others added 2 commits July 14, 2026 21:36
…mbedding setups

- Added a new specification for one-command installer profiles targeting Codex and Claude Code users.
- Implemented profiles for `codex`, `codex-min`, `claude-code`, and `claude-code-hybrid` to streamline installation and configuration.
- Enhanced the CLI with new commands for service management and project initialization.
- Introduced a bootstrap script to facilitate installation without prior Python setup.
- Established per-project memory isolation for Claude Code users, ensuring data privacy across projects.
- Documented the implementation plan and design decisions for future reference and clarity.
…t SDK failures

The lark-oapi ws client's blocking start() never returns after startup:
all receive/reconnect work runs in orphaned asyncio tasks whose failures
are never observed. When the SDK's reconnect task dies (reconnect budget
exhausted, ClientException during reconnect, internal lock leaked by
_connect's early return, or a timeout-less blocking HTTP call wedging its
event loop), the connection stays dead while start() keeps sleeping, so
the channel's restart loop never fires and the bot silently stops
responding until the process is restarted.

Replace the fire-and-forget ws thread with supervised connection
generations: each generation runs a fresh client on its own event loop,
and a watchdog on the channel loop checks connection health, gives the
SDK's auto-reconnect a grace period, then neutralizes the dead client
(disables its reconnect, closes its connection bypassing the leak-prone
internal lock, stops its loop) and rebuilds a clean generation - even if
the old thread is stuck in a blocking call. stop() now performs a real
teardown instead of calling a close() method the SDK client doesn't have.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant