{"record":{"id":"94ecec958f0fb13c","repo":"sipeed/picoclaw","slug":"feishu-image-upload-w","errorCode":null,"errorMessage":"feishu image upload: %w","messagePattern":"feishu image upload: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1149,"sourceCode":"\tif resp.Data != nil && resp.Data.MessageId != nil {\n\t\treturn *resp.Data.MessageId, nil\n\t}\n\treturn \"\", nil\n}\n\n// sendImage uploads an image and sends it as a message.\nfunc (c *FeishuChannel) sendImage(ctx context.Context, chatID string, file *os.File) error {\n\t// Upload image to get image_key\n\tuploadReq := larkim.NewCreateImageReqBuilder().\n\t\tBody(larkim.NewCreateImageReqBodyBuilder().\n\t\t\tImageType(\"message\").\n\t\t\tImage(file).\n\t\t\tBuild()).\n\t\tBuild()\n\n\tuploadResp, err := c.client.Im.V1.Image.Create(ctx, uploadReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"feishu image upload: %w\", err)\n\t}\n\tif !uploadResp.Success() {\n\t\tc.invalidateTokenOnAuthError(uploadResp.Code)\n\t\treturn fmt.Errorf(\"feishu image upload api error (code=%d msg=%s)\", uploadResp.Code, uploadResp.Msg)\n\t}\n\tif uploadResp.Data == nil || uploadResp.Data.ImageKey == nil {\n\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).","sourceCodeStart":1131,"sourceCodeEnd":1167,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1131-L1167","documentation":"The image upload call Im.V1.Image.Create failed at transport level before the API answered (network error, timeout, or an unusable *os.File — the SDK reads the file handle directly). Unlike the text path, the underlying error IS preserved with %w, so the real cause stays visible via errors.As/Unwrap.","triggerScenarios":"DNS/egress failure to open.feishu.cn; the *os.File passed to Image(file) is nil, closed, or at EOF from a prior read; client timeout during a multi-MB upload on a slow link.","commonSituations":"A defer closed the temp file before the upload finished; the same file handle was consumed by a preview/upload elsewhere and never rewound; slow uplink with an aggressive client timeout.","solutions":["Unwrap the cause: errors.As for *url.Error / *net.OpError / *fs.PathError to distinguish network from file problems.","Ensure the file is open and Seek(0,0) to the start before it reaches sendImage.","Retry once for transient network causes; increase the Lark client timeout for uploads.","Confirm proxy/egress settings from the runtime host."],"exampleFix":"// before\nerr := ch.SendMedia(ctx, msg)\nlog.Print(err) // 'feishu image upload: ...' opaque\n\n// after\nerr := ch.SendMedia(ctx, msg)\nvar urlErr *url.Error\nif errors.As(err, &urlErr) && urlErr.Timeout() {\n    // raise upload timeout / retry — cause preserved via %w on this path\n}","handlingStrategy":"retry","validationCode":"if _, serr := file.Stat(); serr != nil {\n    return fmt.Errorf(\"image file unusable: %w\", serr)\n}\nif _, serr := file.Seek(0, io.SeekStart); serr != nil {\n    return fmt.Errorf(\"image file not seekable: %w\", serr)\n}","typeGuard":"func isTransportErr(err error) bool {\n    var opErr *net.OpError\n    return errors.As(err, &opErr)\n}","tryCatchPattern":"err := ch.SendMedia(ctx, msg)\nif err != nil {\n    var opErr *net.OpError\n    if errors.As(err, &opErr) || isTimeout(err) {\n        // transport: retry upload with a freshly opened file handle\n    }\n    // otherwise inspect the message text (api code) or local file error\n}","preventionTips":["Open and Seek(0,0) files right before upload","Keep one file handle per upload attempt; never reuse a consumed handle","Set an upload-sized client timeout, not a request-sized one","Verify egress before batch media sends"],"tags":["feishu","upload","go","network","file-io"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}