sipeed/picoclaw · error · channels.ErrSendFailed
weixin send: %w
Error message
weixin send: %w
What it means
A Weixin text send failed at the HTTP layer and, additionally, the channel is currently in a rate-limit pause (remainingPause() > 0). Because the failure coincides with an active pause, the channel classifies it as permanent: the error wraps channels.ErrSendFailed so the send manager will not requeue the message. Distinguish it from the temporary variant (error 676) with errors.Is(err, channels.ErrTemporary).
Source
Thrown at pkg/channels/weixin/weixin.go:433
contextToken, _ = ct.(string)
}
// If we don't have a context token for this user, we cannot send a valid reply.
// Treat this as a non-temporary error so the manager doesn't keep retrying.
if contextToken == "" {
logger.ErrorCF("weixin", "Missing context token, cannot send message", map[string]any{
"to_user_id": toUserID,
})
return nil, fmt.Errorf("weixin send: %w: missing context token for chat %s", channels.ErrSendFailed, toUserID)
}
if err := c.sendTextMessage(ctx, toUserID, contextToken, msg.Content); err != nil {
logger.ErrorCF("weixin", "Failed to send message", map[string]any{
"to_user_id": toUserID,
"error": err.Error(),
})
if c.remainingPause() > 0 {
return nil, fmt.Errorf("weixin send: %w", channels.ErrSendFailed)
}
return nil, fmt.Errorf("weixin send: %w", channels.ErrTemporary)
}
return nil, nil
}
// VoiceCapabilities returns the voice capabilities of the channel.
func (c *WeixinChannel) VoiceCapabilities() channels.VoiceCapabilities {
return channels.VoiceCapabilities{ASR: true, TTS: true}
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Wait for the pause window to expire, then resend the message manually if it was dropped
- Reduce concurrent sends per user/chat so throttling never arms the pause
- Check upstream error handling: if the message matters, capture failed sends and re-queue them after remainingPause() reaches zero
- If sends keep failing even when unpaused, debug the underlying sendTextMessage error (logged via ErrorCF with to_user_id)
Example fix
// before
if err := ch.Send(ctx, msg); err != nil {
retryForever(msg) // wrong: ErrSendFailed means do NOT retry
}
// after
if err := ch.Send(ctx, msg); err != nil {
if errors.Is(err, channels.ErrTemporary) {
retryLater(msg)
} else {
parkForManualRetry(msg) // permanent (paused or missing token)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// avoid the permanent classification entirely: don't send while paused
if ch.RemainingPause() > 0 {
return scheduleLater(msg) // send after pause -> likely ErrTemporary path, retryable
}
return ch.Send(ctx, msg) Type guard
func classifyWeixinSendErr(err error) (temporary bool) {
return errors.Is(err, channels.ErrTemporary)
}
func isWeixinSendPermanent(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "weixin send:") && errors.Is(err, channels.ErrSendFailed)
} Try / catch
if _, err := ch.Send(ctx, msg); err != nil {
if isWeixinSendPermanent(err) {
parkForManualRetry(msg) // paused: manager will NOT requeue
} else if classifyWeixinSendErr(err) {
requeueWithBackoff(msg)
}
} Prevention
- Never blind-retry sends wrapped in ErrSendFailed — the manager semantics are permanent
- Defer sends while the session pause is active so they classify as temporary instead
- Throttle fan-out replies; every throttle-adjacent failure during a pause becomes permanent
- Log to_user_id with the failure to reconstruct which user lost the message
When it happens
Trigger: sendTextMessage returning an error (network failure, 4xx/5xx from iLink — see error 667) while c.pauseUntil is in the future. Typical ordering: an earlier send got throttled and armed the pause; the retry of that same message fails again while paused, hitting this branch.
Common situations: Reply storm triggering throttle: first failure arms the pause, queued duplicates fail permanently here; long pauses armed by severe throttling converting what would be retryable errors into dropped messages.
Related errors
- weixin session paused (%d min remaining): %w
- %w: %w
- matrix send: %w
- matrix send media: %w
- %s failed: %d %s
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/c2d36ee0c3dbc151.
Report an issue: GitHub.