hermes - 💡(How to fix) Fix Weixin iLink: stale context_token misidentified as rate limit, causing permanent send failure

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

When the Weixin iLink context_token expires, the iLink API returns ret=-2, errmsg="rate limited" instead of the standard session-expired codes (errcode=-14 or errmsg="unknown error"). The current stale-session detection in _is_stale_session_ret() only matches errmsg == "unknown error", so the expired token is never recognized as stale. _is_stale_session_ret() (line ~99) only matches errmsg == "unknown error": return (errmsg or "").lower() == "unknown error" Strip context_token and retry whenever iLink returns ret=-2 for a proactive send, not just for errmsg="unknown error". The tokenless fallback already exists and works (iLink accepts sends without context_token in degraded mode).

Root Cause

In gateway/platforms/weixin.py, _send_chunk() at line ~1605:

is_session_expired = (
    ret == SESSION_EXPIRED_ERRCODE          # -14
    or errcode == SESSION_EXPIRED_ERRCODE   # -14
    or _is_stale_session_ret(ret, errcode, resp.get("errmsg"))
)

_is_stale_session_ret() (line ~99) only matches errmsg == "unknown error":

def _is_stale_session_ret(ret, errcode, errmsg):
    if ret != RATE_LIMIT_ERRCODE and errcode != RATE_LIMIT_ERRCODE:
        return False
    return (errmsg or "").lower() == "unknown error"

But iLink also returns ret=-2, errmsg="rate limited" for expired tokens. This falls through to the rate-limit branch, which retries with the same expired token instead of stripping it.

Fix Action

Workaround

Delete the cached context token file:

echo '{}' > ~/.hermes/weixin/accounts/<account_id>.context-tokens.json

Code Example

is_session_expired = (
    ret == SESSION_EXPIRED_ERRCODE          # -14
    or errcode == SESSION_EXPIRED_ERRCODE   # -14
    or _is_stale_session_ret(ret, errcode, resp.get("errmsg"))
)

---

def _is_stale_session_ret(ret, errcode, errmsg):
    if ret != RATE_LIMIT_ERRCODE and errcode != RATE_LIMIT_ERRCODE:
        return False
    return (errmsg or "").lower() == "unknown error"

---

if (is_rate_limited or is_session_expired) and not retried_without_token and context_token:
    retried_without_token = True
    context_token = None
    # ... strip token and retry
    continue

---

echo '{}' > ~/.hermes/weixin/accounts/<account_id>.context-tokens.json
RAW_BUFFERClick to expand / collapse

Bug

When the Weixin iLink context_token expires, the iLink API returns ret=-2, errmsg="rate limited" instead of the standard session-expired codes (errcode=-14 or errmsg="unknown error"). The current stale-session detection in _is_stale_session_ret() only matches errmsg == "unknown error", so the expired token is never recognized as stale.

Impact

Once the context_token expires, all outbound Weixin messages fail permanently. The retry loop (4 retries with 3x backoff) keeps sending the same expired token, which iLink rejects every time with ret=-2. Cron jobs, push notifications, and any Hermes-initiated messages are all affected. The only recovery is to manually delete the cached context_token JSON file.

Note: inbound messages still work (user can talk to the bot and get replies), because replies use the fresh context_token from the incoming webhook. Only proactive/push sends break.

Root Cause

In gateway/platforms/weixin.py, _send_chunk() at line ~1605:

is_session_expired = (
    ret == SESSION_EXPIRED_ERRCODE          # -14
    or errcode == SESSION_EXPIRED_ERRCODE   # -14
    or _is_stale_session_ret(ret, errcode, resp.get("errmsg"))
)

_is_stale_session_ret() (line ~99) only matches errmsg == "unknown error":

def _is_stale_session_ret(ret, errcode, errmsg):
    if ret != RATE_LIMIT_ERRCODE and errcode != RATE_LIMIT_ERRCODE:
        return False
    return (errmsg or "").lower() == "unknown error"

But iLink also returns ret=-2, errmsg="rate limited" for expired tokens. This falls through to the rate-limit branch, which retries with the same expired token instead of stripping it.

Reproduction

  1. Let the context_token for a Weixin user expire (no incoming messages for an extended period)
  2. Trigger a proactive send (e.g., via cron job)
  3. iLink returns ret=-2, errmsg="rate limited"
  4. All 4 retries fail identically → permanent send failure

Suggested Fix

Strip context_token and retry whenever iLink returns ret=-2 for a proactive send, not just for errmsg="unknown error". The tokenless fallback already exists and works (iLink accepts sends without context_token in degraded mode).

Option A — treat all ret=-2 as potentially stale-token and retry without token first:

if (is_rate_limited or is_session_expired) and not retried_without_token and context_token:
    retried_without_token = True
    context_token = None
    # ... strip token and retry
    continue

Option B — expand _is_stale_session_ret to also match errmsg="rate limited" for proactive sends.

Workaround

Delete the cached context token file:

echo '{}' > ~/.hermes/weixin/accounts/<account_id>.context-tokens.json

Environment

  • Hermes Agent: latest (2026-05-31)
  • Platform: Weixin via iLink (ilinkai.weixin.qq.com)
  • Python: 3.11

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix Weixin iLink: stale context_token misidentified as rate limit, causing permanent send failure