sipeed/picoclaw · warning
feishu placeholder: card build failed: %w
Error message
feishu placeholder: card build failed: %w
What it means
FeishuChannel.SendPlaceholder builds a placeholder interactive card via buildMarkdownCard before any API call; this error means that local conversion failed and the underlying build error is wrapped. The placeholder is a cosmetic UX affordance (random 'thinking...' text), so this failure does not affect the real reply path.
Source
Thrown at pkg/channels/feishu/feishu_64.go:307
}
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 {
return "", fmt.Errorf("feishu placeholder: card build failed: %w", err)
}
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 placeholder send: %w", err)
}
if !resp.Success() {
c.invalidateTokenOnAuthError(resp.Code)
return "", fmt.Errorf("feishu placeholder api error (code=%d msg=%s)", resp.Code, resp.Msg)View on GitHub (pinned to 49183d7e8d)
Solutions
- Inspect the wrapped cause to see which card constraint fired
- Simplify custom placeholder texts to plain strings
- Treat the failure as non-fatal: proceed without a placeholder rather than aborting the reply flow
Example fix
// before
phID, err := ch.SendPlaceholder(ctx, chatID)
if err != nil {
return err // cosmetic feature aborts the whole reply
}
// after
phID, err := ch.SendPlaceholder(ctx, chatID)
if err != nil {
logger.Warn("placeholder skipped", "err", err)
phID = "" // reply flow continues without placeholder
} Defensive patterns
Strategy: fallback
Validate before calling
if !placeholderEnabled { // mirrors c.bc.Placeholder.Enabled check
return // avoids the call entirely
} Type guard
func isPlaceholderBuildFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "placeholder: card build failed")
} Try / catch
phID, err := ch.SendPlaceholder(ctx, chatID)
if err != nil {
logger.Warn("placeholder unavailable, continuing without it", "err", err)
phID = "" // proceed with the normal send path
} Prevention
- Treat the placeholder as optional UX: never let it gate the reply
- Keep custom placeholder texts plain and covered by a card-build unit test
- Gate the call on Placeholder.Enabled to skip the path when unused
- Ignore this error class in alerting; it self-heals on the next message
When it happens
Trigger: SendPlaceholder is called with placeholder text enabled, and buildMarkdownCard fails on the randomly chosen placeholder string (marshal error or card schema violation).
Common situations: A custom placeholder text containing markdown the card builder rejects; library upgrade changing card schema validation; essentially rare - the built-in placeholder strings are known-good.
Related errors
- feishu edit: card build failed: %w
- feishu react: %w
- feishu react api error (code=%d msg=%s)
- feishu send card: %w
- feishu channel is not supported on 32-bit architectures (arm
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/0dedf0b3fe857689.
Report an issue: GitHub.