{"record":{"id":"5e42c7701f7ce76c","repo":"chenhg5/cc-connect","slug":"tuitui-upload-file-w","errorCode":null,"errorMessage":"tuitui: upload file: %w","messagePattern":"tuitui: upload file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/tuitui/tuitui.go","lineNumber":257,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\treturn p.sendMediaID(ctx, rctx, mediaID, name, true)\n}\n\nfunc (p *Platform) SendFile(ctx context.Context, replyCtx any, file core.FileAttachment) error {\n\tname := file.FileName\n\tif name == \"\" {\n\t\tname = \"file\"\n\t}\n\tisImage := strings.HasPrefix(file.MimeType, \"image/\")\n\tmediaType := \"file\"\n\tif isImage {\n\t\tmediaType = \"image\"\n\t}\n\tmediaID, _, err := p.uploadMedia(ctx, file.Data, file.MimeType, name, mediaType)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"tuitui: upload file: %w\", err)\n\t}\n\trctx, err := requireReplyContext(replyCtx)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn p.sendMediaID(ctx, rctx, mediaID, name, isImage)\n}\n\nfunc (p *Platform) ReconstructReplyCtx(sessionKey string) (any, error) {\n\tparts := strings.SplitN(sessionKey, \":\", 3)\n\tif len(parts) < 2 || parts[0] != \"tuitui\" {\n\t\treturn nil, fmt.Errorf(\"tuitui: invalid session key %q\", sessionKey)\n\t}\n\tchatID := parts[1]\n\tif chatID == \"\" {\n\t\treturn nil, fmt.Errorf(\"tuitui: invalid session key %q\", sessionKey)\n\t}\n\treturn replyContext{chatID: chatID, chatType: guessChatType(chatID)}, nil","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/tuitui/tuitui.go#L239-L275","documentation":"SendFile uploads the file (or image, depending on detected type) via uploadMedia and wraps any upload failure as \"tuitui: upload file: %w\". Like the image path, it isolates the media-upload phase from message delivery. The wrapped error contains the true cause.","triggerScenarios":"Calling SendFile with file.Data that uploadMedia rejects: bad or expired credentials, file exceeding size limits, wrong/empty MimeType or filename, network failure to the media endpoint.","commonSituations":"Sharing large logs or build artifacts above TuiTui's upload cap; media API outage; app secret rotated so auth fails; empty file passed from a failed read upstream.","solutions":["Unwrap the error to identify the root cause (auth vs size vs network) and address it.","Check file size against the platform limit and compress/split if necessary before sending.","Verify app credentials and network access to the upload endpoint; retry transient failures with backoff."],"exampleFix":"// before\nerr := p.SendFile(ctx, rctx, core.FileData{FileName: \"build.log\", Data: data, MimeType: \"\"})\n// after\nif len(data) == 0 {\n    return fmt.Errorf(\"refusing to send empty file\")\n}\nerr := p.SendFile(ctx, rctx, core.FileData{FileName: \"build.log\", Data: data, MimeType: \"text/plain\"})","handlingStrategy":"try-catch","validationCode":"if len(file.Data) == 0 || file.FileName == \"\" || len(file.Data) > maxUploadSize {\n    return fmt.Errorf(\"file not uploadable\")\n}","typeGuard":"func sendableFile(f core.FileData) bool {\n    return len(f.Data) > 0 && strings.TrimSpace(f.FileName) != \"\"\n}","tryCatchPattern":"if err := p.SendFile(ctx, rctx, f); err != nil {\n    log.Error(\"file upload failed\", \"cause\", errors.Unwrap(err))\n}","preventionTips":["Check file size against the platform cap before sending large artifacts.","Always set a correct FileName and MimeType.","Verify media endpoint connectivity in deployment health checks."],"tags":["upload","file","network"],"backgroundTag":"api-request-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}