HKUDS/Vibe-Trading · error · RuntimeError

WeChat context_token missing for chat_id={msg.chat_id}, cann

Error message

WeChat context_token missing for chat_id={msg.chat_id}, cannot send

What it means

send() requires a per-conversation context_token for the target chat; after refreshing a stale token via _refresh_context_token_if_stale, an empty token means the channel has no valid context for that chat_id and cannot construct the sendmessage request. WeChat iLink requires this token as an anti-abuse/conversation-integrity parameter.

Source

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

        # Empty progress messages (e.g. after_iteration tool_events) must
        # NOT act as separators — they have no visible content.
        if is_progress and not content and not (msg.media or []):
            self.logger.debug(
                "Skipped empty progress message for {} (no visible content)",
                msg.chat_id,
            )
            return

        # Flush buffered hints before sending any visible message.
        await self._flush_tool_hints(msg.chat_id)

        if not is_progress:
            await self._stop_typing(msg.chat_id, clear_remote=True)

        ctx_token = self._context_tokens.get(msg.chat_id, "")
        ctx_token = await self._refresh_context_token_if_stale(msg.chat_id, ctx_token)
        if not ctx_token:
            raise RuntimeError(
                f"WeChat context_token missing for chat_id={msg.chat_id}, cannot send"
            )

        typing_ticket = ""
        with suppress(Exception):
            typing_ticket = await self._get_typing_ticket(msg.chat_id, ctx_token)

        if typing_ticket:
            with suppress(Exception):
                await self._send_typing(msg.chat_id, typing_ticket, TYPING_STATUS_TYPING)

        typing_keepalive_stop = asyncio.Event()
        typing_keepalive_task: asyncio.Task | None = None
        if typing_ticket:
            typing_keepalive_task = asyncio.create_task(
                self._typing_keepalive_loop(msg.chat_id, typing_ticket, typing_keepalive_stop)
            )

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Ensure the chat has an inbound message history before sending (require user-initiated contact first).
  2. Retry after a short delay if the refresh was rate-limited; log the refresh response for diagnosis.
  3. If restarts lose tokens, persist context_tokens and restore them on boot.
  4. Verify the chat_id is correct — a typo'd or stale chat_id will never have a token.

Example fix

// before
await weixin_channel.send(OutboundMessage(chat_id=target, ...))

// after
if not weixin_channel._context_tokens.get(target):
    await weixin_channel.send(OutboundMessage(chat_id=known_chat, text="Please message me first"))
else:
    await weixin_channel.send(OutboundMessage(chat_id=target, ...))
Defensive patterns

Strategy: validation

Validate before calling

def has_context(ch, chat_id: str) -> bool:
    return bool(ch._context_tokens.get(chat_id))

Try / catch

try:
    await channel.send(msg)
except RuntimeError as e:
    if "context_token missing" in str(e):
        notify_user_to_initiate_contact(msg.chat_id)
    else:
        raise

Prevention

When it happens

Trigger: Sending to a chat_id for which no context_token was ever captured (bot has not received a message in that chat), or the stored token expired and the refresh call returned empty.

Common situations: Proactively messaging a user/chat that has never messaged the bot first (no inbound context to harvest); chat token TTL elapsed during a long pause; process restart losing in-memory _context_tokens dict; WeChat rate-limiting the token refresh endpoint.

Related errors


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