sipeed/picoclaw · error · channels.ErrSendFailed
weixin send media: %w
Error message
weixin send media: %w
What it means
While iterating media parts of a Weixin outbound message, resolveOutboundPart failed to materialize one part's Ref into a local file, so the whole media send aborts, wrapping basechannels.ErrSendFailed. The failing ref is logged (chat_id, ref, error) before returning. Unlike the WeCom channel (error 665's sibling), this path does not fall back to a placeholder — the first unresolvable part stops the entire send.
Source
Thrown at pkg/channels/weixin/media.go:1128
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 {
defer cleanup()
}
kind := outboundMediaKind(part.Type, filename, contentType)
uploaded, uploadErr := c.uploadLocalFile(ctx, localPath, filename, msg.ChatID, kind)
if uploadErr != nil {
err = uploadErr
return
}
err = c.sendUploadedMedia(ctx, msg.ChatID, contextToken, part.Caption, kind, uploaded)
}()
if err != nil {
logger.ErrorCF("weixin", "Failed to send outbound media", map[string]any{
"chat_id": msg.ChatID,
"ref": part.Ref,View on GitHub (pinned to 49183d7e8d)
Solutions
- Pre-validate each part's ref (store lookup or URL HEAD) before calling SendMedia Re-fetch or re-upload the referenced asset and retry the send Drop unresolvable parts and send the remainder plus caption text Fix storage retention: keep resolved files until the send completes (the cleanup func pattern implies files may be temp)
Example fix
// before
ch.SendMedia(ctx, msg) // contains a dead ref -> whole send aborts (error 678)
// after
parts := filterParts(msg.Parts, func(p MediaPart) bool {
_, err := store.Stat(p.Ref)
return err == nil
})
msg.Parts = parts
ch.SendMedia(ctx, msg) Defensive patterns
Strategy: try-catch
Validate before calling
// pre-resolve every part before invoking SendMedia
for _, part := range msg.Parts {
if strings.TrimSpace(part.Ref) == "" {
continue
}
if _, _, _, cleanup, err := ch.ResolveOutboundPart(ctx, part); err != nil {
msg.Parts = dropPart(msg.Parts, part.Ref) // drop instead of failing the whole send
} else if cleanup != nil {
cleanup()
}
} Type guard
func isWeixinMediaResolveFail(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "weixin send media:") && errors.Is(err, basechannels.ErrSendFailed) && strings.Contains(err.Error(), "resolve")
} Try / catch
if _, err := ch.SendMedia(ctx, msg); err != nil {
if isWeixinMediaResolveFail(err) {
// deterministic: filter bad refs, resend remaining parts (captions still go out)
msg.Parts = filterParts(msg.Parts, alive)
_, err = ch.SendMedia(ctx, msg)
}
} Prevention
- Stat/HEAD each ref before building the outbound message
- Expire-tune signed media URLs so they outlive send latency by a wide margin
- Keep resolved temp files until the send finishes; clean only via the provided cleanup func
- Send the first dead part alone shouldn't kill the batch — filter at the application layer
When it happens
Trigger: Any part in msg.Parts whose Ref cannot be resolved: attachment store miss, expired/signed URL, unreachable storage, deleted cache file. The loop at media.go:1118 returns immediately on the first failure.
Common situations: Agent attaches a stale ref from an earlier turn whose backing file was evicted; media URL expired between generation and send; object store briefly down; local tmp cleaned by an aggressive janitor between sessions.
Related errors
- wecom resolve media %q: %v: %w
- weixin send media: missing context token for chat %s: %w
- no media store available: %w
- no media store available: %w
- weixin session paused (%d min remaining): %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/70e166d0b8ea5780.
Report an issue: GitHub.