sipeed/picoclaw · error

feishu delete api error (code=%d msg=%s)

Error message

feishu delete api error (code=%d msg=%s)

What it means

FeishuChannel.deleteMessageAPI's Delete call parsed successfully but resp.Success() is false: Feishu returned a non-zero business code. Frequent codes: 230002 (bot lacks permission or is not in the chat) and invalid/nonexistent message_id (often because the message was already deleted, making this effectively idempotent-success). invalidateTokenOnAuthError(resp.Code) already ran for auth codes.

Source

Thrown at pkg/channels/feishu/feishu_64.go:288

	deleteFn := c.deleteMessageFn
	if deleteFn == nil {
		deleteFn = c.deleteMessageAPI
	}
	return deleteFn(ctx, chatID, messageID)
}

func (c *FeishuChannel) deleteMessageAPI(ctx context.Context, chatID, messageID string) error {
	req := larkim.NewDeleteMessageReqBuilder().
		MessageId(messageID).
		Build()

	resp, err := c.client.Im.V1.Message.Delete(ctx, req)
	if err != nil {
		return fmt.Errorf("feishu delete: %w", err)
	}
	if !resp.Success() {
		c.invalidateTokenOnAuthError(resp.Code)
		return fmt.Errorf("feishu delete api error (code=%d msg=%s)", resp.Code, resp.Msg)
	}
	return nil
}

// SendPlaceholder implements channels.PlaceholderCapable.
// Sends an interactive card with placeholder text and returns its message ID.
func (c *FeishuChannel) SendPlaceholder(ctx context.Context, chatID string) (string, error) {
	if !c.bc.Placeholder.Enabled {
		logger.DebugCF("feishu", "Placeholder disabled, skipping", map[string]any{
			"chat_id": chatID,
		})
		return "", nil
	}

	text := c.bc.Placeholder.GetRandomText()

	cardContent, err := buildMarkdownCard(text)
	if err != nil {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Check the returned code: treat 'message not found' as success - the goal state (message gone) is achieved
  2. 230002: re-add the bot to the chat or grant im:message:delete scope in Feishu Open Platform
  3. Auth codes: token invalidation already fired; verify app credentials if it persists
  4. Deduplicate delete calls so retries do not re-delete

Example fix

// before
if err := ch.DeleteMessage(ctx, chatID, msgID); err != nil {
	return err // 'already deleted' surfaces as a failure
}

// after
err := ch.DeleteMessage(ctx, chatID, msgID)
if err != nil && strings.Contains(err.Error(), "feishu delete api error") {
	logger.Info("delete skipped", "msg_id", msgID, "err", err) // treat as done
	return nil
}
return err
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

var deleteCodeRe = regexp.MustCompile(`delete api error \(code=(\d+) msg=([^)]*)\)`)

func deleteFailureClass(err error) (code int, msg string, apiErr bool) {
	m := deleteCodeRe.FindStringSubmatch(err.Error())
	if m == nil { return 0, "", false }
	n, _ := strconv.Atoi(m[1])
	return n, m[2], true
}

Try / catch

err := ch.DeleteMessage(ctx, chatID, msgID)
if code, msg, apiErr := deleteFailureClass(err); apiErr {
	switch {
	case code == 99991661 || code == 99991663: // auth: token refresh then retry once
	case code == 230002 || strings.Contains(msg, "not found"): // already gone / no perm
		return nil // goal state achieved or unreachable: accept
	}
}
return err

Prevention

When it happens

Trigger: Deleting a message that no longer exists (already deleted), deleting in a chat the bot left, missing im:message:delete scope, or an expired token (99991661/99991663).

Common situations: Placeholder already removed by an earlier retry (duplicate delete); user deleted the message first; bot permissions trimmed after initial setup; deleting across tenants with mismatched app credentials.

Related errors


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