sipeed/picoclaw · error
feishu file send api error (code=%d msg=%s)
Error message
feishu file send api error (code=%d msg=%s)
What it means
Feishu rejected the file message send with a non-zero business code after the upload succeeded. invalidateTokenOnAuthError runs first (clears token on 99991663). Dominant causes: invalid/expired file_key, file_type mismatch between upload and send context, or missing permission on the target chat.
Source
Thrown at pkg/channels/feishu/feishu_64.go:1234
// 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
}
if sender.SenderId.UnionId != nil && *sender.SenderId.UnionId != "" {
return *sender.SenderId.UnionId
}View on GitHub (pinned to 49183d7e8d)
Solutions
- On invalid file_key, re-upload and send immediately as one unit.
- Confirm upload and send use the same app credentials.
- Verify feishuFileType matched the content at upload time (opus/mp4/stream) and the bot is in the target chat.
- If code is 99991663, the token cache was already cleared — next attempt self-heals.
Example fix
// before
key := uploadFile(ctx, f, name, fileType)
err := sendFileMsg(ctx, chatID, key)
// after — pair upload+send so retries always use a fresh, valid key
for attempt := 0; attempt < 2; attempt++ {
key := uploadFile(ctx, f, name, fileType)
err = sendFileMsg(ctx, chatID, key)
if err == nil || !strings.Contains(err.Error(), "code=230002") {
break
}
} Defensive patterns
Strategy: validation
Validate before calling
// validate the pairing before sending: file_key must come from an upload
// made by this app, and feishuFileType must match the uploaded type
if fileKey == "" {
return errors.New("cannot send file message without a fresh file_key")
}
if uploadedType != feishuFileType {
return fmt.Errorf("file_type mismatch: uploaded %s, sending %s", uploadedType, feishuFileType)
} Type guard
func isInvalidFileKey(err error) bool {
return strings.Contains(err.Error(), "feishu file send api error") && strings.Contains(err.Error(), "code=230002")
} Try / catch
if err := sendFileFlow(ctx, chatID, f, name, ft); err != nil {
if isInvalidFileKey(err) {
// expired/mismatched key: re-upload and resend as a unit
return sendFileFlow(ctx, chatID, f, name, ft)
}
return err
} Prevention
- Never reuse file_keys across requests or apps
- Match upload file_type to content (opus/mp4/stream)
- Keep the bot in the target chat with im:message scope
When it happens
Trigger: Message.Create with msg_type=file fails: file_key expired between upload and send; key minted under a different app_id; audio uploaded as opus but sent to a chat/context where opus is disallowed; bot removed from chat; 99991663 stale token.
Common situations: Backoff-delayed retries outliving file_key validity; credential swap between upload and send; sending voice notes to chats where the bot lacks im:message permission.
Related errors
- feishu image send api error (code=%d msg=%s)
- feishu text api error (code=%d msg=%s): %w
- feishu image upload api error (code=%d msg=%s)
- feishu image send: %w
- feishu file upload api error (code=%d msg=%s)
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/10b658fea118bd92.
Report an issue: GitHub.