HKUDS/Vibe-Trading · error · RuntimeError

getUpdates failed: ret={ret} errcode={errcode} errmsg={data.

Error message

getUpdates failed: ret={ret} errcode={errcode} errmsg={data.get('errmsg', '')}

What it means

Raised by the WeChat long-poll loop when the getUpdates-style call returns a non-zero ret or errcode that is not the recognized session-expired code. The library treats session expiry as a pause but surfaces any other poll failure as a RuntimeError including ret, errcode, and errmsg from the API response.

Source

Thrown at agent/src/channels/weixin.py:571

        data = await self._api_post("ilink/bot/getupdates", body)

        # Check for API-level errors (monitor.ts checks both ret and errcode)
        ret = data.get("ret", 0)
        errcode = data.get("errcode", 0)

        is_error = (ret is not None and ret != 0) or (errcode is not None and errcode != 0)

        if is_error:
            if errcode == ERRCODE_SESSION_EXPIRED or ret == ERRCODE_SESSION_EXPIRED:
                self._pause_session()
                remaining = self._session_pause_remaining_s()
                self.logger.warning(
                    "session expired (errcode {}). Pausing {} min.",
                    errcode,
                    max((remaining + 59) // 60, 1),
                )
                return
            raise RuntimeError(
                f"getUpdates failed: ret={ret} errcode={errcode} errmsg={data.get('errmsg', '')}"
            )

        # Honour server-suggested poll timeout (monitor.ts:102-105)
        server_timeout_ms = data.get("longpolling_timeout_ms")
        if server_timeout_ms and server_timeout_ms > 0:
            self._next_poll_timeout_s = max(server_timeout_ms // 1000, 5)

        # Update cursor
        new_buf = data.get("get_updates_buf", "")
        if new_buf:
            self._get_updates_buf = new_buf
            self._save_state()

        # Process messages (WeixinMessage[] from types.ts)
        msgs: list[dict] = data.get("msgs", []) or []
        for msg in msgs:
            try:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Match errcode/ret against WeChat iLink docs in the error message; auth-related codes mean re-login is required.
  2. Implement retry with exponential backoff in the poll loop for transient (network/throttle) codes.
  3. If token invalid, rerun the QR login flow to obtain a fresh session and token.
  4. Verify the request payload (sync key / offset fields) is consistent between polls.

Example fix

// before
await self._poll_once()

// after
try:
    await self._poll_once()
except RuntimeError as e:
    self.logger.warning("poll failed, backing off: {}", e)
    await asyncio.sleep(min(2 ** failures * 5, 300))
Defensive patterns

Strategy: retry

Try / catch

try:
    await channel._poll_once()
except RuntimeError as e:
    if "getUpdates failed" in str(e):
        await asyncio.sleep(min(2 ** failures * 5, 300))
        failures += 1
    else:
        raise

Prevention

When it happens

Trigger: _poll_once (started from start()) receives a poll response where ret != 0 or errcode != 0 and errcode is not ERRCODE_SESSION_EXPIRED — e.g. auth token invalid, malformed sync request, server 5xx disguised as error JSON, or rate limiting.

Common situations: Token revoked or stale after logging in from another device; WeChat server-side changes to the long-poll contract; clock skew or replayed sync keys; heavy polling causing temporary throttling.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/90fe138bd6e85046. Report an issue: GitHub.