fix(weixin): recover from stale context_token on outbound sends - #80426
fix(weixin): recover from stale context_token on outbound sends#80426nothing0here wants to merge 1 commit into
Conversation
iLink reports a stale session as ret=-2 errmsg="prepare failed" on the sendmessage endpoint. The adapter previously classified this as a rate limit, opening the rate-limit circuit and locking out all outbound Weixin delivery (including cron-initiated pushes) until the cooldown expires. Classify "prepare failed" as an outbound stale-context signal, delete the cached context_token only when it still matches the failed token, load the token after acquiring the outbound gate, and allow one tokenless recovery send outside the transient retry budget. If the tokenless recovery also reports a stale session, surface a clear error instead of tripping the rate-limit circuit; genuine rate limits (e.g. "freq limit") still open the breaker. Follow-up to NousResearch#17228; complements NousResearch#74572.
Production validation noteWe independently patched a Hermes 0.16.0 Weixin deployment with the same recovery idea ( Also worth calling out for reviewers: if a Happy to help test / rebase this PR if useful — this looks like the right fix versus “fail fast with a better error” alone. |
Thanks for the production validation — could you help to test / rebase this PR? |
|
Production evidence supporting this PR's shape — three failing windows captured end-to-end on our install (gateway up 8 days, zero inbound for 3 days, full chain in #80125 (comment)): The "Tokenless recovery also reported a stale session" branch is the common path, not an edge case. Every window shows the exact sequence this PR handles: The compare-and-delete has a real race to win. Around an inbound-triggered recovery we diffed the stored token immediately before/after: same length, same prefix, different value — the server issues a new token on inbound rather than the client rewriting it. That means the failure path's Circuit interaction — supporting data for the no-open-on-stale tests. In the pre-fix chain, one One optional refinement: since every captured |
|
Thanks @strzhao — those windows make the “tokenless retry still @nothing0here I won’t take the rebase from here. The branch is still mergeable; please rebase onto current |
What does this PR do?
Fixes outbound Weixin delivery when iLink reports a stale session as
ret=-2, errmsg="prepare failed"onsendmessage. The current adapteronly recognizes
errmsg="unknown error"(orerrcode=-14) as astale-session signal, so
prepare failedfalls through to rate-limithandling: it opens the rate-limit circuit and puts Weixin into a
30-60s cooldown lockout. Long-running agent turns or cron-initiated
pushes that outlive the session regularly hit this, and every follow-up
send then fails fast with a misleading "rate limited" error.
This PR:
prepare failed; the shared poll-path classifier keeps the narrowerunknown errorsemantics;context_tokenonly if it still matches the tokenthat failed (compare-and-delete), so a concurrently refreshed token is
preserved;
fresh inbound token is picked up between chunks;
transient retry budget, and keeps later retries tokenless;
stale sessionerror and does not open the rate-limit circuit;genuine rate limits (e.g.
freq limit) still open the breaker.Related Issue
Follow-up to #17228; complements #74572.
Type of Change
Changes Made
gateway/platforms/weixin.py:_is_stale_context_token_ret()classifier,ContextTokenStore.delete()compare-and-delete, under-gate token load,explicit retry accounting, and no-circuit stale-session error path.
tests/gateway/test_weixin.py: coverage for zero-retry tokenless recovery,multi-chunk tokenless continuation, bounded recovery without original-token
reuse, circuit-not-opened on stale-session failure, concurrent fresh-token
preservation, under-gate token load, classifier semantics, and
ContextTokenStore.delete()persistence.How to Test
ret=-2 errmsg="prepare failed", then attempt an outbound send(e.g.
hermes send -t weixin "hello").iLink sendmessage rate limited; cooldown active for 30.0s/60.0sand subsequent sends fail fast while the circuit is open.session expired ... retrying without context_token;if the tokenless recovery succeeds the message is delivered, otherwise a
clear
stale session ... wait for a new inbound messageerror is raisedand no cooldown is opened.
pytest tests/gateway/test_weixin.py -q-> 38 passed;ruff check gateway/platforms/weixin.py tests/gateway/test_weixin.py-> clean.
Checklist
Code
pytest tests/ -qand all tests pass(weixin suite and ruff are green on this host; full repo suite not run)
Documentation & Housekeeping
Screenshots / Logs
Production log after the fix (stale startup notification):
No
cooldown activeline is emitted for the stale-session case.