Summary
When the OpenClaw agent initiates an outbound message (not as a reply to an inbound WeChat message), sendMessage returns ret=-2 errmsg= with no error description. This happens because the context_token used in the outbound call has expired.
Observed Behavior
- Inbound reply path (agent responds to a user's WeChat message): works reliably. The
context_token from the latest inbound getUpdates response is fresh.
- Agent-initiated outbound (e.g., cron-triggered news delivery, system-triggered reminders): consistently fails with
ret=-2 after ~48 hours of no inbound WeChat messages.
Empirical Timeline
| Period |
Delivery Status |
Issue |
| Jul 5 & earlier |
❌ silent failure |
sendMessage returned non-zero but error wasn't parsed |
| Jul 6–7 mornings |
✅ success |
Fresh context_token after user inbound activity |
| Jul 7 evenings |
❌ ret=-2 |
Token expired |
| Jul 9 morning |
✅ success |
User sent inbound message, token refreshed |
| Jul 10–11 all |
❌ ret=-2 |
No inbound activity → token expired |
Pattern: deliveries succeed for ~1 day after the last inbound WeChat message, then fail with ret=-2 until a new inbound message refreshes the token.
Root Cause
The context_token is obtained from inbound getUpdates responses and stored as a per-conversation token. According to the Tencent iLink API protocol, context_token has a limited lifetime (~48 hours based on empirical observation). When it expires, sendMessage returns ret=-2 with an empty errmsg.
The plugin has no mechanism to:
- Detect that
ret=-2 means "token expired" — the empty errmsg makes it non-obvious vs other ret codes
- Refresh the token (e.g., by calling
getUpdates or getConfig with no sync cursor to get a fresh context_token)
- Fall back to sending without a
context_token (which the API may accept for new conversation contexts)
- Retry after token refresh
Affected Use Cases
- Any cron/agent-initiated outbound delivery (scheduled news, reminders, notifications)
- Messages sent during long periods of user inactivity on WeChat
Proposed Fix Options
Option A: Refresh token on ret=-2 (recommended)
When sendMessage returns ret=-2, the plugin should:
- Call
getUpdates with an empty sync cursor to obtain a fresh context_token (establishes a new conversation context)
- Retry the
sendMessage with the new token
Option B: Fall back to no context_token
Test whether omitting context_token from the sendMessage request body causes the API to start a new conversation context. If so, use this as a fallback when the token is stale.
Option C: Proactive token refresh
Track context_token age and proactively refresh it via periodic getUpdates calls when approaching the expiration window (~36 hours).
Environment
Additional Context
The sendMessage response looks like:
Note that errmsg is empty, making the error code the only signal. This contrasts with other errors like errcode=-14 (session timeout) in the getUpdates endpoint which do include a descriptive message.
The context_token is documented in the API protocol:
- Inbound: included in
getUpdates response msg.context_token
- Outbound: must be passed back in
sendMessage request msg.context_token
But its lifetime and failure mode are not documented.
Summary
When the OpenClaw agent initiates an outbound message (not as a reply to an inbound WeChat message),
sendMessagereturnsret=-2 errmsg=with no error description. This happens because thecontext_tokenused in the outbound call has expired.Observed Behavior
context_tokenfrom the latest inboundgetUpdatesresponse is fresh.ret=-2after ~48 hours of no inbound WeChat messages.Empirical Timeline
sendMessagereturned non-zero but error wasn't parsedcontext_tokenafter user inbound activityPattern: deliveries succeed for ~1 day after the last inbound WeChat message, then fail with
ret=-2until a new inbound message refreshes the token.Root Cause
The
context_tokenis obtained from inboundgetUpdatesresponses and stored as a per-conversation token. According to the Tencent iLink API protocol,context_tokenhas a limited lifetime (~48 hours based on empirical observation). When it expires,sendMessagereturnsret=-2with an emptyerrmsg.The plugin has no mechanism to:
ret=-2means "token expired" — the emptyerrmsgmakes it non-obvious vs otherretcodesgetUpdatesorgetConfigwith no sync cursor to get a freshcontext_token)context_token(which the API may accept for new conversation contexts)Affected Use Cases
Proposed Fix Options
Option A: Refresh token on ret=-2 (recommended)
When
sendMessagereturnsret=-2, the plugin should:getUpdateswith an empty sync cursor to obtain a freshcontext_token(establishes a new conversation context)sendMessagewith the new tokenOption B: Fall back to no context_token
Test whether omitting
context_tokenfrom thesendMessagerequest body causes the API to start a new conversation context. If so, use this as a fallback when the token is stale.Option C: Proactive token refresh
Track
context_tokenage and proactively refresh it via periodicgetUpdatescalls when approaching the expiration window (~36 hours).Environment
@tencent-weixin/openclaw-weixinv2.4.3 (also observed on v2.4.6)openclaw(https://github.com/openclaw/openclaw)Additional Context
The
sendMessageresponse looks like:Note that
errmsgis empty, making the error code the only signal. This contrasts with other errors likeerrcode=-14(session timeout) in thegetUpdatesendpoint which do include a descriptive message.The
context_tokenis documented in the API protocol:getUpdatesresponsemsg.context_tokensendMessagerequestmsg.context_tokenBut its lifetime and failure mode are not documented.