sipeed/picoclaw · error · channels.ErrSendFailed

weixin send media: missing context token for chat %s: %w

Error message

weixin send media: missing context token for chat %s: %w

What it means

The media send path of the Weixin channel refuses to send because no context_token is stored for msg.ChatID — the same gating as error 674 but for media parts. The iLink media upload/send calls need the token captured from the user's last inbound message; without it the channel fails fast (wrapping basechannels.ErrSendFailed) before resolving or uploading any files.

Source

Thrown at pkg/channels/weixin/media.go:1113

	return stop, nil
}

// SendMedia implements channels.MediaSender.
func (c *WeixinChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) ([]string, error) {
	if !c.IsRunning() {
		return nil, basechannels.ErrNotRunning
	}
	if err := c.ensureSessionActive(); err != nil {
		return nil, err
	}

	contextToken := ""
	if v, ok := c.contextTokens.Load(msg.ChatID); ok {
		contextToken, _ = v.(string)
	}
	if contextToken == "" {
		return nil, fmt.Errorf(
			"weixin send media: missing context token for chat %s: %w",
			msg.ChatID,
			basechannels.ErrSendFailed,
		)
	}

	for _, part := range msg.Parts {
		localPath, filename, contentType, cleanup, err := c.resolveOutboundPart(ctx, part)
		if err != nil {
			logger.ErrorCF("weixin", "Failed to resolve outbound media", map[string]any{
				"chat_id": msg.ChatID,
				"ref":     part.Ref,
				"error":   err.Error(),
			})
			return nil, fmt.Errorf("weixin send media: %w", basechannels.ErrSendFailed)
		}
		func() {
			if cleanup != nil {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Ensure an inbound message from the chat exists before sending media (reply-only semantics) After restarts, have users send any message to re-arm the token, then resend the media Verify msg.ChatID equals the inbound user id exactly (no mapping drift) Send the payload as text/URL if the user cannot be asked to re-engage

Example fix

// before
ch.SendMedia(ctx, msg) // msg.ChatID never seen inbound -> error 677

// after
if _, ok := ch.ContextToken(msg.ChatID); !ok {
    return skipMediaReply(msg.ChatID)
}
ch.SendMedia(ctx, msg)
Defensive patterns

Strategy: validation

Validate before calling

// gate media sends on token availability (export ContextToken)
if _, ok := ch.ContextToken(msg.ChatID); !ok {
    return skipMedia(msg.ChatID) // nothing to send without an inbound history
}
return ch.SendMedia(ctx, msg)

Type guard

func isWeixinMediaMissingToken(err error) bool {
    return err != nil && strings.Contains(err.Error(), "weixin send media: missing context token")
}

Try / catch

if _, err := ch.SendMedia(ctx, msg); err != nil {
    if isWeixinMediaMissingToken(err) {
        // non-retryable: stash the media; deliver on the user's next inbound
        parkUntilInbound(msg)
    }
}

Prevention

When it happens

Trigger: Sending media to a chat that has not messaged the bot since channel start; after a restart (tokens are in-memory, c.contextTokens is cleared); using a ChatID that differs from the key stored on inbound.

Common situations: Bot tries to proactively push an image/file to a user who never wrote; restart wiped tokens so media replies to old threads fail until the user pings again; media reply routed through a rewritten chat id.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/faebf40ffbfe4033. Report an issue: GitHub.