{"record":{"id":"343a97acc5f73574","repo":"sipeed/picoclaw","slug":"no-media-store-available-w","errorCode":null,"errorMessage":"no media store available: %w","messagePattern":"no media store available: %w","errorType":"exception","errorClass":"ErrSendFailed","httpStatus":null,"severity":"error","filePath":"pkg/channels/discord/discord.go","lineNumber":274,"sourceCode":"\t}\n\treturn vc, true\n}\n\n// SendMedia implements the channels.MediaSender interface.\nfunc (c *DiscordChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) ([]string, error) {\n\tif !c.IsRunning() {\n\t\treturn nil, channels.ErrNotRunning\n\t}\n\n\tchannelID := msg.ChatID\n\tif channelID == \"\" {\n\t\treturn nil, fmt.Errorf(\"channel ID is empty\")\n\t}\n\ttrackedMsgID, hasTrackedMsg := c.currentToolFeedbackMessage(channelID)\n\n\tstore := c.GetMediaStore()\n\tif store == nil {\n\t\treturn nil, fmt.Errorf(\"no media store available: %w\", channels.ErrSendFailed)\n\t}\n\n\t// Collect all files into a single ChannelMessageSendComplex call\n\tfiles := make([]*discordgo.File, 0, len(msg.Parts))\n\tvar caption string\n\n\tfor _, part := range msg.Parts {\n\t\tlocalPath, err := store.Resolve(part.Ref)\n\t\tif err != nil {\n\t\t\tlogger.ErrorCF(\"discord\", \"Failed to resolve media ref\", map[string]any{\n\t\t\t\t\"ref\":   part.Ref,\n\t\t\t\t\"error\": err.Error(),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tfile, err := os.Open(localPath)\n\t\tif err != nil {","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/discord/discord.go#L256-L292","documentation":"SendMedia needs a media store to resolve each part.Ref into a readable local file before uploading to Discord; GetMediaStore() returned nil, meaning SetMediaStore was never called on the channel. It wraps channels.ErrSendFailed, so the manager treats it as permanent and will not retry. The message text also carries ErrSendFailed via %w.","triggerScenarios":"Constructing and starting the Discord channel without attaching a media store — e.g. embedding the channel outside the Manager that normally calls Manager.SetMediaStore (manager.go), or a startup ordering where media arrives before the store is attached.","commonSituations":"Custom embeddings of PicoClaw channels that skip manager wiring, tests exercising SendMedia without a store (see qq_test's equivalent), disabling the media subsystem in config while the agent still emits images/files.","solutions":["Attach a media store before any media send: ch.SetMediaStore(store) with a media.MediaStore implementation","If using the standard Manager, ensure it is the one constructing/starting channels so SetMediaStore propagates to existing channels","Check startup ordering: attach the store before the agent can publish OutboundMediaMessage"],"exampleFix":"// before\nch, _ := discord.NewDiscordChannel(bc, cfg, bus)\n_ = ch.Start(ctx) // later SendMedia fails: no media store available\n\n// after\nch, _ := discord.NewDiscordChannel(bc, cfg, bus)\nch.SetMediaStore(store) // media.MediaStore implementation\n_ = ch.Start(ctx)","handlingStrategy":"validation","validationCode":"if ch.GetMediaStore() == nil {\n    return fmt.Errorf(\"discord channel has no media store; attach one before sending media\")\n}","typeGuard":"func hasMediaStore(ch interface{ GetMediaStore() media.MediaStore }) bool {\n    return ch.GetMediaStore() != nil\n}","tryCatchPattern":"if _, err := ch.SendMedia(ctx, msg); err != nil {\n    if errors.Is(err, channels.ErrSendFailed) && strings.Contains(err.Error(), \"no media store\") {\n        // permanent: wire the store, do not retry\n        ch.SetMediaStore(store)\n        return ch.SendMedia(ctx, msg)\n    }\n    return err\n}","preventionTips":["Construct channels through the Manager so SetMediaStore propagates automatically","Attach the media store before Start / before the agent can emit media","Smoke-test SendMedia once at startup to fail fast on missing wiring"],"tags":["discord","media","configuration","send-failed"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}