{"record":{"id":"10e17c7048b75b39","repo":"sipeed/picoclaw","slug":"discord-send-w","errorCode":null,"errorMessage":"discord send: %w","messagePattern":"discord send: %w","errorType":"exception","errorClass":"ErrTemporary","httpStatus":null,"severity":"error","filePath":"pkg/channels/discord/discord.go","lineNumber":502,"sourceCode":"\t\t\terr error\n\t\t)\n\n\t\t// If we have an ID, we send the message as \"Reply\"\n\t\tif replyToID != \"\" {\n\t\t\tmsg, err = c.session.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{\n\t\t\t\tContent: content,\n\t\t\t\tReference: &discordgo.MessageReference{\n\t\t\t\t\tMessageID: replyToID,\n\t\t\t\t\tChannelID: channelID,\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\t// Otherwise, we send a normal message\n\t\t\tmsg, err = c.session.ChannelMessageSend(channelID, content)\n\t\t}\n\n\t\tif err != nil {\n\t\t\tdone <- result{err: fmt.Errorf(\"discord send: %w\", channels.ErrTemporary)}\n\t\t\treturn\n\t\t}\n\t\tdone <- result{id: msg.ID}\n\t}()\n\n\tselect {\n\tcase r := <-done:\n\t\treturn r.id, r.err\n\tcase <-sendCtx.Done():\n\t\treturn \"\", sendCtx.Err()\n\t}\n}\n\n// appendContent safely appends content to existing text\nfunc appendContent(content, suffix string) string {\n\tif content == \"\" {\n\t\treturn suffix\n\t}","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/discord/discord.go#L484-L520","documentation":"sendChunk's ChannelMessageSend / ChannelMessageSendComplex (used for reply references) failed inside the goroutine; wrapped as channels.ErrTemporary so the manager retries with backoff. The raw discordgo error is discarded, so the visible error never says whether it was a 403 permission, 400 bad request, or network failure.","triggerScenarios":"Content exceeding Discord's 2000-char limit (the channel sets MaxMessageLength 2000 and normally splits first), missing SEND_MESSAGES permission, deleted/archived/gone channelID, replying to a deleted message via MessageReference, network drop within the 10s sendTimeout.","commonSituations":"Bot kicked from server or channel permissions tightened, very long messages bypassing the splitter, replies racing with message deletion, transient Discord/network issues.","solutions":["Verify the bot can send in the target channel (permissions, channel exists, not archived)","Confirm content length is under 2000 runes so the splitter is doing its job","Patch the wrap to preserve the cause: fmt.Errorf(\"discord send: %w: %w\", channels.ErrTemporary, err)","Log err inside the goroutine before sending to done so failures are diagnosable in logs"],"exampleFix":"// before\nif err != nil {\n    done <- result{err: fmt.Errorf(\"discord send: %w\", channels.ErrTemporary)}\n    return\n}\n\n// after\nif err != nil {\n    logger.WarnCF(\"discord\", \"send failed\", map[string]any{\"channel\": channelID, \"error\": err.Error()})\n    done <- result{err: fmt.Errorf(\"discord send: %w: %w\", channels.ErrTemporary, err)}\n    return\n}","handlingStrategy":"retry","validationCode":"if len([]rune(content)) > 2000 {\n    content = content[:2000] // or split — channel MaxMessageLength is 2000\n}","typeGuard":"func isTemporarySend(err error) bool { return errors.Is(err, channels.ErrTemporary) }","tryCatchPattern":"if _, err := ch.Send(ctx, msg); err != nil {\n    if errors.Is(err, channels.ErrTemporary) {\n        // backoff-retry once; permission/oversize failures repeat forever — inspect logs\n    }\n    return err\n}","preventionTips":["Respect the 2000-rune limit by always going through the splitter","Check bot permissions in the target channel after permission changes","Patch the wrap to include the discordgo error so 403 vs 400 vs network is visible","Log the underlying err inside the send goroutine before reporting ErrTemporary"],"tags":["discord","send","temporary","retry"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}