sipeed/picoclaw · error · channels.ErrSendFailed
wecom resolve media %q: %v: %w
Error message
wecom resolve media %q: %v: %w
What it means
Raised while preparing a WeCom outbound message: resolveOutboundPart failed to turn a message part's Ref into a (localPath, filename, contentType, cleanup) tuple — the referenced attachment could not be fetched/materialized locally. The error chains the underlying cause plus channels.ErrSendFailed, so upstream retry logic treats it as a permanent send failure. Only the ref-resolution step aborts; later upload failures fall back to a placeholder instead.
Source
Thrown at pkg/channels/wecom/wecom.go:245
route, chatType, hasTurn := c.resolveMediaRoute(msg.ChatID)
chatID := route.ChatID
if chatID == "" {
chatID = msg.ChatID
}
for _, part := range msg.Parts {
if strings.TrimSpace(part.Ref) == "" {
if caption := strings.TrimSpace(part.Caption); caption != "" {
if err := c.sendActivePush(chatID, chatType, caption); err != nil {
return nil, err
}
}
continue
}
localPath, filename, contentType, cleanup, err := c.resolveOutboundPart(ctx, part)
if err != nil {
return nil, fmt.Errorf("wecom resolve media %q: %v: %w", part.Ref, err, channels.ErrSendFailed)
}
func() {
if cleanup != nil {
defer cleanup()
}
uploaded, uploadErr := c.uploadOutboundMedia(ctx, localPath, filename, contentType, part)
if uploadErr != nil {
logger.WarnCF("wecom", "Falling back to placeholder after media upload failure", map[string]any{
"chat_id": chatID,
"ref": part.Ref,
"filename": filename,
"content_type": contentType,
"error": uploadErr.Error(),
})
if hasTurn {
if finishErr := c.sendStreamChunk(route, true, ""); finishErr != nil {View on GitHub (pinned to 49183d7e8d)
Solutions
- Verify the ref exists in the attachment store before queueing the send (lookup/HEAD the ref)
- If the ref is a URL, check that it is still valid and reachable from the host (curl it); re-upload or re-sign if expired
- Ensure the media cache/storage directory the resolver uses is present and writable
- Drop the broken part and resend — text parts without Refs still deliver (captions are sent as active pushes)
- If refs routinely expire, shorten the gap between generation and send or persist the binary instead of the ref
Example fix
// before
parts := []channels.MediaPart{{Ref: oldRef}} // oldRef no longer resolvable -> error 665
// after
if _, err := store.Stat(oldRef); err != nil {
parts = nil // drop dead ref, send text only
}
send(parts...) Defensive patterns
Strategy: try-catch
Validate before calling
// resolve refs before building the outbound message
for _, part := range msg.Parts {
if strings.TrimSpace(part.Ref) == "" {
continue
}
if _, err := store.Stat(part.Ref); err != nil {
// for URL refs: HEAD the URL and check 200
return fmt.Errorf("part ref %q unresolvable: %w", part.Ref, err)
}
} Type guard
func isWecomResolveMedia(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "wecom resolve media")
} Try / catch
if _, err := ch.Send(ctx, msg); err != nil {
if isWecomResolveMedia(err) && errors.Is(err, channels.ErrSendFailed) {
// permanent: strip bad parts and resend text-only instead of retrying
}
} Prevention
- Verify refs exist in the attachment store before queueing sends
- Set adequate TTLs on signed media URLs relative to send latency
- Retain resolved temp files until the send completes (cleanup runs post-upload)
- Drop dead refs and still deliver caption/text parts rather than failing the whole message
When it happens
Trigger: A message part with a non-empty Ref whose backing asset cannot be resolved: unknown/expired reference in the attachment store, download of the referenced URL failing, storage backend unreachable, or the ref pointing at a file that no longer exists. The send loop at wecom.go:245 wraps any such failure with this message and stops processing remaining parts.
Common situations: The agent replies with an attachment ref produced in an earlier session that has since been evicted; the media URL is behind auth or has expired (signed URL TTL passed); local cache/tmp directory was cleaned; object store outage; a ref typo in a manually constructed message.
Related errors
- weixin send media: %w
- no media store available: %w
- no media store available: %w
- read media: %w
- media too large
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/7ed13c6b9c36f3cd.
Report an issue: GitHub.