sipeed/picoclaw · error · ErrTemporary

feishu send card: %w

Error message

feishu send card: %w

What it means

FeishuChannel.sendCard called lark SDK Im.V1.Message.Create (interactive card) and the SDK failed at the transport level; the wrapper returns the ErrTemporary sentinel. Note the SDK error itself is NOT wrapped - callers see only the retryable class and must infer/inspect the cause elsewhere. Retry policy per channels package: exponential backoff 500ms*2^attempt, max 8s.

Source

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

		return tag
	}
	return content + " " + tag
}

// sendCard sends an interactive card message to a chat.
func (c *FeishuChannel) sendCard(ctx context.Context, chatID, cardContent string) (string, error) {
	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(chatID).
			MsgType(larkim.MsgTypeInteractive).
			Content(cardContent).
			Build()).
		Build()

	resp, err := c.client.Im.V1.Message.Create(ctx, req)
	if err != nil {
		return "", fmt.Errorf("feishu send card: %w", channels.ErrTemporary)
	}

	if !resp.Success() {
		c.invalidateTokenOnAuthError(resp.Code)
		return "", fmt.Errorf("feishu api error (code=%d msg=%s): %w", resp.Code, resp.Msg, channels.ErrTemporary)
	}

	logger.DebugCF("feishu", "Feishu card message sent", map[string]any{
		"chat_id": chatID,
	})

	if resp.Data != nil && resp.Data.MessageId != nil {
		return *resp.Data.MessageId, nil
	}
	return "", nil
}

// sendText sends a plain text message to a chat (fallback when card fails).

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Retry with exponential backoff (up to 8s) - the sentinel deliberately marks this retryable
  2. If persistent, capture the underlying SDK error at the sendCard call site (it is currently discarded) to diagnose
  3. Shrink card content: split long tables or truncate to keep payloads lean
  4. Check network/proxy health to open.feishu.cn

Example fix

// before (pkg/channels/feishu/feishu_64.go) - cause discarded
if err != nil {
	return "", fmt.Errorf("feishu send card: %w", channels.ErrTemporary)
}

// after - preserve the cause for diagnosis
if err != nil {
	return "", fmt.Errorf("feishu send card: %w: %w", channels.ErrTemporary, err)
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

func isTemporaryCardSend(err error) bool {
	return err != nil && errors.Is(err, channels.ErrTemporary)
}

Try / catch

var err error
for attempt := 0; attempt < 4; attempt++ {
	_, err = sendViaChannel(ctx, msg)
	if !isTemporaryCardSend(err) { break }
	time.Sleep(min(500*time.Millisecond<<attempt, 8*time.Second))
}
return err

Prevention

When it happens

Trigger: POST create-message fails at the network layer while sending a card: timeout, connection reset, DNS failure, oversized card body stalling the request.

Common situations: Transient egress issues; very large markdown tables inflating card JSON past intermediary timeouts; burst sends saturating connections; Feishu edge blips.

Related errors


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