{"record":{"id":"126c81831ad674d2","repo":"sipeed/picoclaw","slug":"feishu-send-card-w","errorCode":null,"errorMessage":"feishu send card: %w","messagePattern":"feishu send card: %w","errorType":"exception","errorClass":"ErrTemporary","httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1087,"sourceCode":"\t\treturn tag\n\t}\n\treturn content + \" \" + tag\n}\n\n// sendCard sends an interactive card message to a chat.\nfunc (c *FeishuChannel) sendCard(ctx context.Context, chatID, cardContent string) (string, error) {\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 send card: %w\", channels.ErrTemporary)\n\t}\n\n\tif !resp.Success() {\n\t\tc.invalidateTokenOnAuthError(resp.Code)\n\t\treturn \"\", fmt.Errorf(\"feishu api error (code=%d msg=%s): %w\", resp.Code, resp.Msg, channels.ErrTemporary)\n\t}\n\n\tlogger.DebugCF(\"feishu\", \"Feishu card message sent\", map[string]any{\n\t\t\"chat_id\": chatID,\n\t})\n\n\tif resp.Data != nil && resp.Data.MessageId != nil {\n\t\treturn *resp.Data.MessageId, nil\n\t}\n\treturn \"\", nil\n}\n\n// sendText sends a plain text message to a chat (fallback when card fails).","sourceCodeStart":1069,"sourceCodeEnd":1105,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1069-L1105","documentation":"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.","triggerScenarios":"POST create-message fails at the network layer while sending a card: timeout, connection reset, DNS failure, oversized card body stalling the request.","commonSituations":"Transient egress issues; very large markdown tables inflating card JSON past intermediary timeouts; burst sends saturating connections; Feishu edge blips.","solutions":["Retry with exponential backoff (up to 8s) - the sentinel deliberately marks this retryable","If persistent, capture the underlying SDK error at the sendCard call site (it is currently discarded) to diagnose","Shrink card content: split long tables or truncate to keep payloads lean","Check network/proxy health to open.feishu.cn"],"exampleFix":"// before (pkg/channels/feishu/feishu_64.go) - cause discarded\nif err != nil {\n\treturn \"\", fmt.Errorf(\"feishu send card: %w\", channels.ErrTemporary)\n}\n\n// after - preserve the cause for diagnosis\nif err != nil {\n\treturn \"\", fmt.Errorf(\"feishu send card: %w: %w\", channels.ErrTemporary, err)\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"func isTemporaryCardSend(err error) bool {\n\treturn err != nil && errors.Is(err, channels.ErrTemporary)\n}","tryCatchPattern":"var err error\nfor attempt := 0; attempt < 4; attempt++ {\n\t_, err = sendViaChannel(ctx, msg)\n\tif !isTemporaryCardSend(err) { break }\n\ttime.Sleep(min(500*time.Millisecond<<attempt, 8*time.Second))\n}\nreturn err","preventionTips":["Honor the sentinel's contract: exponential backoff, capped at 8s, limited attempts","Trim oversized markdown tables before sending - big cards cause slow-request timeouts misread as transient","Note the wrapper drops the SDK cause; add temporary logging at the sendCard site when diagnosing","Stop retrying after the budget: sustained ErrTemporary signals an outage, not luck"],"tags":["feishu","network","card","temporary","retry","error-masking"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}