{"record":{"id":"0dedf0b3fe857689","repo":"sipeed/picoclaw","slug":"feishu-placeholder-card-build-failed-w","errorCode":null,"errorMessage":"feishu placeholder: card build failed: %w","messagePattern":"feishu placeholder: card build failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":307,"sourceCode":"\t}\n\treturn nil\n}\n\n// SendPlaceholder implements channels.PlaceholderCapable.\n// Sends an interactive card with placeholder text and returns its message ID.\nfunc (c *FeishuChannel) SendPlaceholder(ctx context.Context, chatID string) (string, error) {\n\tif !c.bc.Placeholder.Enabled {\n\t\tlogger.DebugCF(\"feishu\", \"Placeholder disabled, skipping\", map[string]any{\n\t\t\t\"chat_id\": chatID,\n\t\t})\n\t\treturn \"\", nil\n\t}\n\n\ttext := c.bc.Placeholder.GetRandomText()\n\n\tcardContent, err := buildMarkdownCard(text)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"feishu placeholder: card build failed: %w\", err)\n\t}\n\n\treq := larkim.NewCreateMessageReqBuilder().\n\t\tReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).\n\t\tBody(larkim.NewCreateMessageReqBodyBuilder().\n\t\t\tReceiveId(chatID).\n\t\t\tMsgType(larkim.MsgTypeInteractive).\n\t\t\tContent(cardContent).\n\t\t\tBuild()).\n\t\tBuild()\n\n\tresp, err := c.client.Im.V1.Message.Create(ctx, req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"feishu placeholder send: %w\", err)\n\t}\n\tif !resp.Success() {\n\t\tc.invalidateTokenOnAuthError(resp.Code)\n\t\treturn \"\", fmt.Errorf(\"feishu placeholder api error (code=%d msg=%s)\", resp.Code, resp.Msg)","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L289-L325","documentation":"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.","triggerScenarios":"SendPlaceholder is called with placeholder text enabled, and buildMarkdownCard fails on the randomly chosen placeholder string (marshal error or card schema violation).","commonSituations":"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.","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"],"exampleFix":"// before\nphID, err := ch.SendPlaceholder(ctx, chatID)\nif err != nil {\n\treturn err // cosmetic feature aborts the whole reply\n}\n\n// after\nphID, err := ch.SendPlaceholder(ctx, chatID)\nif err != nil {\n\tlogger.Warn(\"placeholder skipped\", \"err\", err)\n\tphID = \"\" // reply flow continues without placeholder\n}","handlingStrategy":"fallback","validationCode":"if !placeholderEnabled { // mirrors c.bc.Placeholder.Enabled check\n\treturn // avoids the call entirely\n}","typeGuard":"func isPlaceholderBuildFailure(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"placeholder: card build failed\")\n}","tryCatchPattern":"phID, err := ch.SendPlaceholder(ctx, chatID)\nif err != nil {\n\tlogger.Warn(\"placeholder unavailable, continuing without it\", \"err\", err)\n\tphID = \"\" // proceed with the normal send path\n}","preventionTips":["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"],"tags":["feishu","card","placeholder","non-fatal","serialization"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}