sipeed/picoclaw · error
feishu image send: %w
Error message
feishu image send: %w
What it means
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.
Source
Thrown at pkg/channels/feishu/feishu_64.go:1174
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).
Content(string(content)).
Build()).
Build()
resp, err := c.client.Im.V1.Message.Create(ctx, req)
if err != nil {
return fmt.Errorf("feishu image send: %w", err)
}
if !resp.Success() {
c.invalidateTokenOnAuthError(resp.Code)
return fmt.Errorf("feishu image send api error (code=%d msg=%s)", resp.Code, resp.Msg)
}
return nil
}
// sendFile uploads a file and sends it as a message.
func (c *FeishuChannel) sendFile(ctx context.Context, chatID string, file *os.File, filename, fileType string) error {
// Map part type to Feishu file type
feishuFileType := "stream"
switch fileType {
case "audio":
feishuFileType = "opus"
case "video":
feishuFileType = "mp4"
}View on GitHub (pinned to 49183d7e8d)
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.
Example fix
// before
err := ch.SendMedia(ctx, msg)
// after
mediaCtx, cancel := context.WithTimeout(ctx, 60*time.Second)
defer cancel()
err := ch.SendMedia(mediaCtx, msg)
if errors.Is(err, context.DeadlineExceeded) {
// retry the whole upload+send flow with a fresh key
} Defensive patterns
Strategy: retry
Validate before calling
mediaCtx, cancel := context.WithTimeout(ctx, 60*time.Second) defer cancel() // pass mediaCtx so upload+send share one generous deadline
Type guard
func isTimeout(err error) bool {
var urlErr *url.Error
return errors.As(err, &urlErr) && urlErr.Timeout()
} Try / catch
if err := ch.SendMedia(ctx, msg); err != nil {
if isTimeout(err) || errors.Is(err, context.DeadlineExceeded) {
// retry the whole flow — but expect the image_key may now be expired (code 230002 on next send)
}
} Prevention
- 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
When it happens
Trigger: Connection drop or client timeout between upload and send; the ctx cancelled mid-request; SDK error building the msg_type=image request.
Common situations: 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.
Related errors
- feishu file send: %w
- feishu image upload: %w
- feishu image send api error (code=%d msg=%s)
- feishu file upload: %w
- feishu file send api error (code=%d msg=%s)
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/e68f899f68a966af.
Report an issue: GitHub.