chenhg5/cc-connect · error

weixin: missing context_token for peer %q - user must send a

Error message

weixin: missing context_token for peer %q - user must send a message to the bot first

What it means

WeChat's ilink sendmessage API requires a context_token captured from the user's most recent inbound message. If the reply context has no context_token and none is cached for the peer user, sendChunks refuses to send and returns this error, since any send attempt would be rejected by the server.

Source

Thrown at platform/weixin/weixin.go:820

}

func (p *Platform) sendChunks(ctx context.Context, replyCtx any, content string, path sendPath) error {
	rc, ok := replyCtx.(*replyContext)
	if !ok || rc == nil {
		return fmt.Errorf("weixin: invalid reply context")
	}
	if err := p.checkSendQuota(ctx, path); err != nil {
		return err
	}
	if strings.TrimSpace(rc.contextToken) == "" {
		rc.contextToken = p.getContextToken(rc.peerUserID)
	}
	if strings.TrimSpace(rc.contextToken) == "" {
		slog.Error("weixin: cannot send message - missing context_token",
			"peer", rc.peerUserID,
			"content_preview", truncatePreview(content, 100),
			"hint", "user needs to send a message to the bot first so a context_token can be captured")
		return fmt.Errorf("weixin: missing context_token for peer %q - user must send a message to the bot first", rc.peerUserID)
	}
	if strings.TrimSpace(content) == "" {
		return nil
	}
	chunks := splitUTF8(content, maxWeixinChunk)
	total := len(chunks)
	for i, chunk := range chunks {
		// Add delay between chunks to avoid rate limiting (except for first chunk)
		if i > 0 {
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-time.After(weixinChunkSendDelay):
			}
		}
		err := p.sendChunk(ctx, rc, chunk)
		if err != nil {
			slog.Error("weixin: chunk send failed, message incomplete",

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Ask the user to send any message to the bot first, then reply within the token validity window
  2. Persist state_dir so context tokens survive restarts
  3. Convert proactive pushes into replies triggered by user activity
  4. Reduce push volume so tokens don't expire between sends
Defensive patterns

Strategy: fallback

Validate before calling

if strings.TrimSpace(rc.ContextToken) == "" {
    // cannot push; queue message until the user messages the bot
    queueForLaterReply(peerUserID, content)
    return nil
}

Try / catch

if err := p.Send(ctx, rc, msg); err != nil && strings.Contains(err.Error(), "missing context_token") {
    log.Warn("user must message the bot first; queuing", "peer", rc.PeerUserID)
    queueForLaterReply(rc.PeerUserID, msg)
}

Prevention

When it happens

Trigger: The bot attempts a proactive push (Send, cron, timer) to a peer who has never messaged the bot, or whose captured context_token is missing/expired from the token cache and sync buffer.

Common situations: Bot restarted with an empty state_dir losing cached tokens; user's context_token expired after inactivity; first-ever message to a new user attempted as a push instead of waiting for an inbound message.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/4cfaecea348cbf4b. Report an issue: GitHub.