sipeed/picoclaw · error
feishu image upload: %w
Error message
feishu image upload: %w
What it means
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.
Source
Thrown at pkg/channels/feishu/feishu_64.go:1149
if resp.Data != nil && resp.Data.MessageId != nil {
return *resp.Data.MessageId, nil
}
return "", nil
}
// sendImage uploads an image and sends it as a message.
func (c *FeishuChannel) sendImage(ctx context.Context, chatID string, file *os.File) error {
// Upload image to get image_key
uploadReq := larkim.NewCreateImageReqBuilder().
Body(larkim.NewCreateImageReqBodyBuilder().
ImageType("message").
Image(file).
Build()).
Build()
uploadResp, err := c.client.Im.V1.Image.Create(ctx, uploadReq)
if err != nil {
return fmt.Errorf("feishu image upload: %w", err)
}
if !uploadResp.Success() {
c.invalidateTokenOnAuthError(uploadResp.Code)
return fmt.Errorf("feishu image upload api error (code=%d msg=%s)", uploadResp.Code, uploadResp.Msg)
}
if uploadResp.Data == nil || uploadResp.Data.ImageKey == nil {
return fmt.Errorf("feishu image upload: no image_key returned")
}
imageKey := *uploadResp.Data.ImageKey
// Send image message
content, _ := json.Marshal(map[string]string{"image_key": imageKey})
req := larkim.NewCreateMessageReqBuilder().
ReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).
Body(larkim.NewCreateMessageReqBodyBuilder().
ReceiveId(chatID).
MsgType(larkim.MsgTypeImage).View on GitHub (pinned to 49183d7e8d)
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.
Example fix
// before
err := ch.SendMedia(ctx, msg)
log.Print(err) // 'feishu image upload: ...' opaque
// after
err := ch.SendMedia(ctx, msg)
var urlErr *url.Error
if errors.As(err, &urlErr) && urlErr.Timeout() {
// raise upload timeout / retry — cause preserved via %w on this path
} Defensive patterns
Strategy: retry
Validate before calling
if _, serr := file.Stat(); serr != nil {
return fmt.Errorf("image file unusable: %w", serr)
}
if _, serr := file.Seek(0, io.SeekStart); serr != nil {
return fmt.Errorf("image file not seekable: %w", serr)
} Type guard
func isTransportErr(err error) bool {
var opErr *net.OpError
return errors.As(err, &opErr)
} Try / catch
err := ch.SendMedia(ctx, msg)
if err != nil {
var opErr *net.OpError
if errors.As(err, &opErr) || isTimeout(err) {
// transport: retry upload with a freshly opened file handle
}
// otherwise inspect the message text (api code) or local file error
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- feishu file upload: %w
- feishu image send: %w
- feishu file send: %w
- feishu send text: %w
- feishu image upload api error (code=%d msg=%s)
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/94ecec958f0fb13c.
Report an issue: GitHub.