sipeed/picoclaw · error

feishu file send: %w

Error message

feishu file send: %w

What it means

After a successful file upload, the Message.Create that sends the file_key failed at transport level (network error, timeout, cancelled context). The underlying error is preserved with %w. As with images, a delayed retry risks the file_key expiring.

Source

Thrown at pkg/channels/feishu/feishu_64.go:1230

		return fmt.Errorf("feishu file upload: no file_key returned")
	}

	fileKey := *uploadResp.Data.FileKey

	// Send file message
	content, _ := json.Marshal(map[string]string{"file_key": fileKey})
	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(chatID).
			MsgType(larkim.MsgTypeFile).
			Content(string(content)).
			Build()).
		Build()

	resp, err := c.client.Im.V1.Message.Create(ctx, req)
	if err != nil {
		return fmt.Errorf("feishu file send: %w", err)
	}
	if !resp.Success() {
		c.invalidateTokenOnAuthError(resp.Code)
		return fmt.Errorf("feishu file send api error (code=%d msg=%s)", resp.Code, resp.Msg)
	}
	return nil
}

func extractFeishuSenderID(sender *larkim.EventSender) string {
	if sender == nil || sender.SenderId == nil {
		return ""
	}

	if sender.SenderId.UserId != nil && *sender.SenderId.UserId != "" {
		return *sender.SenderId.UserId
	}
	if sender.SenderId.OpenId != nil && *sender.SenderId.OpenId != "" {
		return *sender.SenderId.OpenId

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Unwrap the cause to confirm transport vs context cancellation.
  2. Retry the send; if it then reports an invalid file_key (see 492), redo upload+send as one unit.
  3. Use one generous deadline for the whole upload+send flow.
  4. Check egress stability for large payloads.

Example fix

// before
err := ch.SendMedia(ctx, msg)

// after
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
    // caller cancelled or deadline hit: retry with fresh ctx and fresh file handle
}
var urlErr *url.Error
if errors.As(err, &urlErr) && urlErr.Timeout() {
    // transport timeout: raise timeout, then retry upload+send together
}
Defensive patterns

Strategy: retry

Validate before calling

mediaCtx, cancel := context.WithTimeout(ctx, 90*time.Second) // room for 30MB upload + send
defer cancel()

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.Canceled) {
        // retry the full upload+send; a lone send retry may hit an expired file_key
    }
}

Prevention

When it happens

Trigger: Connection drop or timeout between file upload and message send; ctx cancelled; SDK error building the msg_type=file request.

Common situations: Slow networks uploading near the 30MB cap then timing out on send; ctx deadline exhausted by the upload step; pod restart mid-flow.

Related errors


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