{"record":{"id":"aab548743383589a","repo":"chenhg5/cc-connect","slug":"tuitui-upload-image-w","errorCode":null,"errorMessage":"tuitui: upload image: %w","messagePattern":"tuitui: upload image: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/tuitui/tuitui.go","lineNumber":236,"sourceCode":"\t\t\treturn err\n\t\t}\n\t\tteamID := stringFromAny(info[\"team_id\"])\n\t\tif teamID == \"\" {\n\t\t\treturn fmt.Errorf(\"tuitui: team_id not found for channel %q\", channelID)\n\t\t}\n\t\tchatID = teamsBuildChatID(teamID, channelID, strings.TrimSpace(parentID))\n\t}\n\treturn p.sendText(ctx, replyContext{chatID: chatID, chatType: chatTypeChannel}, markdown)\n}\n\nfunc (p *Platform) SendImage(ctx context.Context, replyCtx any, img core.ImageAttachment) error {\n\tname := img.FileName\n\tif name == \"\" {\n\t\tname = \"image\"\n\t}\n\tmediaID, _, err := p.uploadMedia(ctx, img.Data, img.MimeType, name, \"image\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"tuitui: upload image: %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, 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}","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/tuitui/tuitui.go#L218-L254","documentation":"SendImage first uploads the image bytes to TuiTui to obtain a media ID; if uploadMedia fails, the error is wrapped as \"tuitui: upload image: %w\". This separates upload failures (auth, size, network, unsupported mime) from the later send step. The underlying cause is always available via errors.Unwrap.","triggerScenarios":"Calling SendImage with img.Data that the upload endpoint rejects: expired/invalid token, oversized file, unsupported or empty MimeType, or network failure during uploadMedia.","commonSituations":"Sending a very large screenshot exceeding the media size limit; app credentials revoked or rotated; offline/proxied environment blocking the media upload endpoint; sending a file whose content-type does not match an image.","solutions":["Inspect the wrapped cause (errors.Unwrap / %w chain) to see whether it was auth, size, or network, and fix accordingly.","Validate the image: non-empty Data, reasonable size, correct image MimeType before calling SendImage.","Refresh app credentials (appID/appSecret) and retry; add retry with backoff for transient network errors."],"exampleFix":"// before\nerr := p.SendImage(ctx, rctx, core.ImageData{Data: hugeBlob, MimeType: \"\"})\n// after\nif len(hugeBlob) > maxUploadSize || mimeType == \"\" {\n    return fmt.Errorf(\"image invalid: size=%d mime=%q\", len(hugeBlob), mimeType)\n}\nerr := p.SendImage(ctx, rctx, core.ImageData{Data: hugeBlob, MimeType: mimeType})","handlingStrategy":"try-catch","validationCode":"if len(img.Data) == 0 || img.MimeType == \"\" || len(img.Data) > maxUploadSize {\n    return fmt.Errorf(\"image not uploadable\")\n}","typeGuard":"func sendableImage(img core.ImageData) bool {\n    return len(img.Data) > 0 && img.MimeType != \"\"\n}","tryCatchPattern":"if err := p.SendImage(ctx, rctx, img); err != nil {\n    var cause error\n    errors.As(err, &cause)\n    log.Error(\"image upload failed\", \"cause\", cause)\n}","preventionTips":["Enforce size and mime-type limits before upload.","Keep app credentials valid and rotate them through config, not code.","Add exponential backoff for transient upload failures."],"tags":["upload","image","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"}