{"record":{"id":"94731b0a2a312ee4","repo":"chenhg5/cc-connect","slug":"start-thread-for-message-s-w","errorCode":null,"errorMessage":"start thread for message %s: %w","messagePattern":"start thread for message (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/discord/discord.go","lineNumber":415,"sourceCode":"\t\tthreadID := m.Message.Thread.ID\n\t\tif err := ops.JoinThread(threadID); err != nil {\n\t\t\tslog.Debug(\"discord: join attached thread failed\", \"thread\", threadID, \"error\", err)\n\t\t}\n\t\trc := replyContext{channelID: threadID, messageID: m.ID, threadID: threadID}\n\t\treturn buildThreadSessionKey(threadID), rc, m.ChannelID, nil\n\t}\n\tif m.Flags&discordgo.MessageFlagsHasThread != 0 {\n\t\tthreadID := m.ID\n\t\tif err := ops.JoinThread(threadID); err != nil {\n\t\t\tslog.Debug(\"discord: join message thread failed\", \"thread\", threadID, \"error\", err)\n\t\t}\n\t\trc := replyContext{channelID: threadID, messageID: m.ID, threadID: threadID}\n\t\treturn buildThreadSessionKey(threadID), rc, m.ChannelID, nil\n\t}\n\n\tthread, err := ops.StartThread(m.ChannelID, m.ID, threadNameForMessage(m, botID), 1440)\n\tif err != nil {\n\t\treturn \"\", replyContext{}, \"\", fmt.Errorf(\"start thread for message %s: %w\", m.ID, err)\n\t}\n\tif err := ops.JoinThread(thread.ID); err != nil {\n\t\tslog.Debug(\"discord: join new thread failed\", \"thread\", thread.ID, \"error\", err)\n\t}\n\trc := replyContext{channelID: thread.ID, messageID: m.ID, threadID: thread.ID}\n\treturn buildThreadSessionKey(thread.ID), rc, m.ChannelID, nil\n}\n\nfunc resolveCronReplyTarget(sessionKey, title string, ops discordThreadOps) (string, replyContext, error) {\n\tchannelID, err := parseDiscordSessionKeyChannelID(sessionKey)\n\tif err != nil {\n\t\treturn \"\", replyContext{}, err\n\t}\n\n\tch, err := ops.ResolveChannel(channelID)\n\tif err != nil {\n\t\treturn \"\", replyContext{}, fmt.Errorf(\"resolve channel %s: %w\", channelID, err)\n\t}","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/discord/discord.go#L397-L433","documentation":"For a guild message that is not already inside a thread, resolveThreadReplyContext starts a new thread on the message via ops.StartThread. If that Discord API call fails, the error is wrapped as \"start thread for message <messageID>: <cause>\" and message dispatch is aborted. Thread creation can fail for permissions, archived/locked parent threads, or API errors.","triggerScenarios":"ops.StartThread(m.ChannelID, m.ID, name, 1440) fails: bot lacks \"Create Public Threads\" permission, the message is older than 48 hours, the channel is not a guild text channel, the channel/thread is archived or locked, or Discord returns 429/5xx.","commonSituations":"Bots added with default permissions lacking thread management rights, messages arriving in announcement/forum channels where thread start rules differ, long-running cron sessions trying to thread an old message, rate limits during high traffic.","solutions":["Read the wrapped cause; grant the bot \"Create Public Threads\" and \"Send Messages in Threads\" permissions.","Confirm the message is <48h old and in a guild text channel eligible for threading.","Unarchive/unlock the parent thread or post to the channel directly instead of threading.","Retry on 429 with backoff, or fall back to replying in the parent channel when thread creation fails."],"exampleFix":"// before\nthread, err := ops.StartThread(m.ChannelID, m.ID, threadNameForMessage(m, botID), 1440)\nif err != nil {\n    return \"\", replyContext{}, \"\", fmt.Errorf(\"start thread for message %s: %w\", m.ID, err)\n}\n// after\nthread, err := ops.StartThread(m.ChannelID, m.ID, threadNameForMessage(m, botID), 1440)\nif err != nil {\n    slog.Warn(\"discord: thread start failed, falling back to channel\", \"message\", m.ID, \"error\", err)\n    rc := replyContext{channelID: m.ChannelID, messageID: m.ID}\n    return buildThreadSessionKey(m.ChannelID), rc, m.ChannelID, nil\n}","handlingStrategy":"fallback","validationCode":"// preflight: bot can create threads in this channel?\nperm, err := session.State.UserChannelPermissions(botID, m.ChannelID)\ncanThread := err == nil && perm&discordgo.PermissionCreatePublicThreads != 0","typeGuard":null,"tryCatchPattern":"thread, err := ops.StartThread(m.ChannelID, m.ID, name, 1440)\nif err != nil {\n    slog.Warn(\"discord: falling back to channel reply\", \"message\", m.ID, \"error\", err)\n    return buildThreadSessionKey(m.ChannelID), replyContext{channelID: m.ChannelID, messageID: m.ID}, m.ChannelID, nil\n}","preventionTips":["Grant the bot Create Public Threads and Send Messages in Threads permissions.","Only attempt threading on messages younger than 48 hours in guild text channels.","Design replies to degrade gracefully to the parent channel when threading fails."],"tags":["discord","thread-creation","permissions"],"backgroundTag":"permission-denied","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}