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

  1. On invalid file_key, re-upload and send immediately as one unit.
  2. Confirm upload and send use the same app credentials.
  3. Verify feishuFileType matched the content at upload time (opus/mp4/stream) and the bot is in the target chat.
  4. 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

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


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