sipeed/picoclaw · error · ErrSendFailed
no media store available: %w
Error message
no media store available: %w
What it means
FeishuChannel.SendMedia requires a media store (to fetch/cache the bytes of each media part) and got nil from GetMediaStore(). This is a wiring/dependency error, not a runtime one: the channel was composed without a media store, so every media send will fail. Wrapped with the permanent ErrSendFailed sentinel.
Source
Thrown at pkg/channels/feishu/feishu_64.go:501
}
return undo, nil
}
// SendMedia implements channels.MediaSender.
// Uploads images/files via Feishu API then sends as messages.
func (c *FeishuChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) ([]string, error) {
if !c.IsRunning() {
return nil, channels.ErrNotRunning
}
trackedMsgID, hasTrackedMsg := c.currentToolFeedbackMessage(msg.ChatID)
if msg.ChatID == "" {
return nil, fmt.Errorf("chat ID is empty: %w", channels.ErrSendFailed)
}
store := c.GetMediaStore()
if store == nil {
return nil, fmt.Errorf("no media store available: %w", channels.ErrSendFailed)
}
caption := firstMediaCaption(msg.Parts)
sentAny := false
for _, part := range msg.Parts {
if err := c.sendMediaPartFn(ctx, msg.ChatID, part, store); err != nil {
return nil, err
}
sentAny = true
}
if sentAny && caption != "" {
if _, err := c.sendTextFn(ctx, msg.ChatID, caption); err != nil {
return nil, err
}
}
if hasTrackedMsg {
c.dismissTrackedToolFeedbackMessage(ctx, msg.ChatID, trackedMsgID)View on GitHub (pinned to 49183d7e8d)
Solutions
- Wire a media store into the channel during composition (SetMediaStore / the package's bootstrap helper) before dispatching media
- Assert media readiness at startup: fail fast if media messages can reach a channel without a store
- Until wired, route media replies to a text fallback instead of sending to this channel
Example fix
// before ch, _ := feishu.NewFeishuChannel(cfg) // no media store wired bus.SubscribeMedia(ch.SendMedia) // every media send fails // after ch, _ := feishu.NewFeishuChannel(cfg) ch.SetMediaStore(mystore) // injected at composition bus.SubscribeMedia(ch.SendMedia)
Defensive patterns
Strategy: validation
Validate before calling
// capability check at composition time, before any message flows
if !ch.HasMediaStore() { // or: ch.GetMediaStore() == nil via the public interface
logger.Fatal("feishu channel used for media without a media store")
}
bus.SubscribeMedia(ch.SendMedia) Type guard
null
Try / catch
if err := ch.SendMedia(ctx, msg); err != nil {
if errors.Is(err, channels.ErrSendFailed) && strings.Contains(err.Error(), "no media store") {
// wiring bug: fall back to text-only notice instead of failing silently
_, _ = ch.Send(ctx, bus.OutboundMessage{ChatID: msg.ChatID, Content: "[media unavailable]"})
return nil
}
return err
} Prevention
- Assert required dependencies at composition time (fail fast, not on first media send)
- Reuse the package's bootstrap helper rather than constructing channels manually
- Add a startup self-test that sends/round-trips a tiny media message in staging
- Make wiring bugs loud: this error repeats for every media message until fixed
When it happens
Trigger: SendMedia is invoked on a FeishuChannel whose media store was never injected (GetMediaStore() == nil) - typically a custom composition root or test harness that constructs the channel directly instead of via the standard bootstrap.
Common situations: Embedding the channel in your own main() and forgetting SetMediaStore; DI container misconfiguration; feature-flagged media support disabled at composition time but media messages still routed to the channel.
Related errors
- no media store available: %w
- chat ID is empty: %w
- feishu send media: %w
- no media store available: %w
- wecom resolve media %q: %v: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/2d70a795f4f8b650.
Report an issue: GitHub.