{"record":{"id":"da08025ec5ed55ca","repo":"sipeed/picoclaw","slug":"feishu-file-send-w","errorCode":null,"errorMessage":"feishu file send: %w","messagePattern":"feishu file send: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/feishu/feishu_64.go","lineNumber":1230,"sourceCode":"\t\treturn fmt.Errorf(\"feishu file upload: no file_key returned\")\n\t}\n\n\tfileKey := *uploadResp.Data.FileKey\n\n\t// Send file message\n\tcontent, _ := json.Marshal(map[string]string{\"file_key\": fileKey})\n\treq := larkim.NewCreateMessageReqBuilder().\n\t\tReceiveIdType(larkim.CreateMessageV1ReceiveIDTypeChatId).\n\t\tBody(larkim.NewCreateMessageReqBodyBuilder().\n\t\t\tReceiveId(chatID).\n\t\t\tMsgType(larkim.MsgTypeFile).\n\t\t\tContent(string(content)).\n\t\t\tBuild()).\n\t\tBuild()\n\n\tresp, err := c.client.Im.V1.Message.Create(ctx, req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"feishu file send: %w\", err)\n\t}\n\tif !resp.Success() {\n\t\tc.invalidateTokenOnAuthError(resp.Code)\n\t\treturn fmt.Errorf(\"feishu file send api error (code=%d msg=%s)\", resp.Code, resp.Msg)\n\t}\n\treturn nil\n}\n\nfunc extractFeishuSenderID(sender *larkim.EventSender) string {\n\tif sender == nil || sender.SenderId == nil {\n\t\treturn \"\"\n\t}\n\n\tif sender.SenderId.UserId != nil && *sender.SenderId.UserId != \"\" {\n\t\treturn *sender.SenderId.UserId\n\t}\n\tif sender.SenderId.OpenId != nil && *sender.SenderId.OpenId != \"\" {\n\t\treturn *sender.SenderId.OpenId","sourceCodeStart":1212,"sourceCodeEnd":1248,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/feishu/feishu_64.go#L1212-L1248","documentation":"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.","triggerScenarios":"Connection drop or timeout between file upload and message send; ctx cancelled; SDK error building the msg_type=file request.","commonSituations":"Slow networks uploading near the 30MB cap then timing out on send; ctx deadline exhausted by the upload step; pod restart mid-flow.","solutions":["Unwrap the cause to confirm transport vs context cancellation.","Retry the send; if it then reports an invalid file_key (see 492), redo upload+send as one unit.","Use one generous deadline for the whole upload+send flow.","Check egress stability for large payloads."],"exampleFix":"// before\nerr := ch.SendMedia(ctx, msg)\n\n// after\nif errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n    // caller cancelled or deadline hit: retry with fresh ctx and fresh file handle\n}\nvar urlErr *url.Error\nif errors.As(err, &urlErr) && urlErr.Timeout() {\n    // transport timeout: raise timeout, then retry upload+send together\n}","handlingStrategy":"retry","validationCode":"mediaCtx, cancel := context.WithTimeout(ctx, 90*time.Second) // room for 30MB upload + send\ndefer cancel()","typeGuard":"func isTimeout(err error) bool {\n    var urlErr *url.Error\n    return errors.As(err, &urlErr) && urlErr.Timeout()\n}","tryCatchPattern":"if err := ch.SendMedia(ctx, msg); err != nil {\n    if isTimeout(err) || errors.Is(err, context.Canceled) {\n        // retry the full upload+send; a lone send retry may hit an expired file_key\n    }\n}","preventionTips":["One deadline for the whole media flow","Pair upload+send in retries","Keep the file available until send confirms"],"tags":["feishu","send","go","network","upload"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}