{"record":{"id":"e68f899f68a966af","repo":"sipeed/picoclaw","slug":"feishu-image-send-w","errorCode":null,"errorMessage":"feishu image send: %w","messagePattern":"feishu image send: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1174,"sourceCode":"\t\treturn fmt.Errorf(\"feishu image upload: no image_key returned\")\n\t}\n\n\timageKey := *uploadResp.Data.ImageKey\n\n\t// Send image message\n\tcontent, _ := json.Marshal(map[string]string{\"image_key\": imageKey})\n\treq := larkim.NewCreateMessageReqBuilder().\n\t\tReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).\n\t\tBody(larkim.NewCreateMessageReqBodyBuilder().\n\t\t\tReceiveId(chatID).\n\t\t\tMsgType(larkim.MsgTypeImage).\n\t\t\tContent(string(content)).\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 image send: %w\", err)\n\t}\n\tif !resp.Success() {\n\t\tc.invalidateTokenOnAuthError(resp.Code)\n\t\treturn fmt.Errorf(\"feishu image send api error (code=%d msg=%s)\", resp.Code, resp.Msg)\n\t}\n\treturn nil\n}\n\n// sendFile uploads a file and sends it as a message.\nfunc (c *FeishuChannel) sendFile(ctx context.Context, chatID string, file *os.File, filename, fileType string) error {\n\t// Map part type to Feishu file type\n\tfeishuFileType := \"stream\"\n\tswitch fileType {\n\tcase \"audio\":\n\t\tfeishuFileType = \"opus\"\n\tcase \"video\":\n\t\tfeishuFileType = \"mp4\"\n\t}","sourceCodeStart":1156,"sourceCodeEnd":1192,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1156-L1192","documentation":"After a successful upload, the follow-up Message.Create that sends the image_key failed at transport level (network error, timeout, closed connection). The underlying error is preserved with %w. The upload itself already consumed the image, so a retry must re-send the message only while the image_key is still valid.","triggerScenarios":"Connection drop or client timeout between upload and send; the ctx cancelled mid-request; SDK error building the msg_type=image request.","commonSituations":"Long GC pause or pod eviction between upload and send; mobile/spot network; ctx deadline set too tight for the two-step upload+send flow.","solutions":["Unwrap the cause (url.Error/net.OpError) to confirm transport vs context cancellation.","Retry the send step; if the retry reports an invalid image_key (see 487), redo upload+send as one unit.","Bound the whole media flow (upload+send) with one generous ctx deadline instead of two tight ones."],"exampleFix":"// before\nerr := ch.SendMedia(ctx, msg)\n\n// after\nmediaCtx, cancel := context.WithTimeout(ctx, 60*time.Second)\ndefer cancel()\nerr := ch.SendMedia(mediaCtx, msg)\nif errors.Is(err, context.DeadlineExceeded) {\n    // retry the whole upload+send flow with a fresh key\n}","handlingStrategy":"retry","validationCode":"mediaCtx, cancel := context.WithTimeout(ctx, 60*time.Second)\ndefer cancel()\n// pass mediaCtx so upload+send share one generous deadline","typeGuard":"func isTimeout(err error) bool {\n    var urlErr *url.Error\n    return errors.As(err, &urlErr) && urlErr.Timeout()\n}","tryCatchPattern":"if err := ch.SendMedia(ctx, msg); err != nil {\n    if isTimeout(err) || errors.Is(err, context.DeadlineExceeded) {\n        // retry the whole flow — but expect the image_key may now be expired (code 230002 on next send)\n    }\n}","preventionTips":["Bound upload+send with one deadline","Retry image flow as a unit (upload then send), not the send alone","Keep the file handle open across the flow until send completes"],"tags":["feishu","send","go","network","upload"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}