chenhg5/cc-connect · error

discord: send button followup: %w

Error message

discord: send button followup: %w

What it means

For interaction reply contexts, SendWithButtons first sends the interaction response, then posts the button message as a followup via FollowupMessageCreate; this error wraps any failure of that followup webhook call. The initial interaction reply succeeded, but the followup (which carries the buttons) failed. Root cause lives in the wrapped discordgo error.

Source

Thrown at platform/discord/discord.go:1197

	if len(buttons) == 0 {
		return fmt.Errorf("discord: no buttons provided")
	}
	components := buildDiscordActionRows(buttons)
	if len(components) == 0 {
		return fmt.Errorf("discord: no buttons provided")
	}

	switch rc := rctx.(type) {
	case *interactionReplyCtx:
		if err := p.sendInteraction(rc, content); err != nil {
			return err
		}
		_, err := p.session.FollowupMessageCreate(rc.interaction, true, &discordgo.WebhookParams{
			Content:    content,
			Components: components,
		})
		if err != nil {
			return fmt.Errorf("discord: send button followup: %w", err)
		}
		return nil
	case replyContext:
		_, err := p.session.ChannelMessageSendComplex(rc.targetChannelID(), &discordgo.MessageSend{
			Content:    content,
			Components: components,
		})
		if err != nil {
			return fmt.Errorf("discord: send channel buttons: %w", err)
		}
		return nil
	default:
		return core.ErrNotSupported
	}
}

func (p *progressPlatform) ProgressUpdateInterval() time.Duration {
	return 2 * time.Second

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the wrapped error: 404 unknown webhook means the interaction token expired — re-send as a normal channel message instead
  2. Send the followup promptly after replying to the interaction, well within 15 minutes
  3. Fall back to ChannelMessageSendComplex with components when the followup fails
  4. Verify bot SEND_MESSAGES/VIEW_CHANNEL permissions in the target channel

Example fix

// before
_, err := p.session.FollowupMessageCreate(rc.interaction, true, params)
// after
_, err := p.session.FollowupMessageCreate(rc.interaction, true, params)
if err != nil {
    _, err = p.session.ChannelMessageSendComplex(rc.channelID, &discordgo.MessageSend{Content: content, Components: components})
}
Defensive patterns

Strategy: try-catch

Validate before calling

if time.Since(rc.interactionCreatedAt) > 14*time.Minute {
    return errors.New("interaction token near expiry; use channel send instead")
}

Try / catch

err := p.SendWithButtons(ctx, rctx, content, buttons)
if err != nil {
    var restErr *discordgo.RESTError
    if errors.As(err, &restErr) && restErr.Message.Code == 10060 /* unknown webhook */ {
        // interaction token expired: fall back to channel message
        return sendChannelFallback(ctx, content, buttons)
    }
    return err
}

Prevention

When it happens

Trigger: Interaction token expired (webhook tokens are valid 15 minutes after the interaction) or was already consumed; the bot lacks permission in the channel; Discord API returned 4xx/5xx to the webhook endpoint.

Common situations: Long-running agent work delayed the followup past the 15-minute interaction-token lifetime; bot kicked from channel between replies; transient network/API outage mid-conversation.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/d297d56bfa2f8cd7. Report an issue: GitHub.