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

  1. Wire a media store into the channel during composition (SetMediaStore / the package's bootstrap helper) before dispatching media
  2. Assert media readiness at startup: fail fast if media messages can reach a channel without a store
  3. 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

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


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