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

  1. Unwrap the cause: errors.As for *url.Error / *net.OpError / *fs.PathError to distinguish network from file problems.
  2. Ensure the file is open and Seek(0,0) to the start before it reaches sendImage.
  3. Retry once for transient network causes; increase the Lark client timeout for uploads.
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/94ecec958f0fb13c. Report an issue: GitHub.