{"record":{"id":"d9aa8e0920455c12","repo":"xpzouying/xiaohongshu-mcp","slug":"error-d9aa8e","errorCode":null,"errorMessage":"小红书上传图片失败","messagePattern":"小红书上传图片失败","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"xiaohongshu/publish.go","lineNumber":82,"sourceCode":"\t}\n\n\ttime.Sleep(1 * time.Second)\n\n\treturn &PublishAction{\n\t\tpage: pp,\n\t}, nil\n}\n\nfunc (p *PublishAction) Publish(ctx context.Context, content PublishImageContent) error {\n\tif len(content.ImagePaths) == 0 {\n\t\treturn errors.New(\"图片不能为空\")\n\t}\n\n\t// 重设超时：.Context(ctx) 会替换掉 NewPublishImageAction 里 Timeout(300s) 的 deadline\n\tpage := p.page.Context(ctx).Timeout(300 * time.Second)\n\n\tif err := uploadImages(page, content.ImagePaths); err != nil {\n\t\treturn errors.Wrap(err, \"小红书上传图片失败\")\n\t}\n\n\ttags := content.Tags\n\tif len(tags) >= 10 {\n\t\tlogrus.Warnf(\"标签数量超过10，截取前10个标签\")\n\t\ttags = tags[:10]\n\t}\n\n\tlogrus.Infof(\"发布内容: title=%s, images=%v, tags=%v, schedule=%v, original=%v, visibility=%s, products=%v\", content.Title, len(content.ImagePaths), tags, content.ScheduleTime, content.IsOriginal, content.Visibility, content.Products)\n\n\tif err := submitPublish(ctx, page, content.Title, content.Content, tags, content.ScheduleTime, content.IsOriginal, content.Visibility, content.Products); err != nil {\n\t\treturn errors.Wrap(err, \"小红书发布失败\")\n\t}\n\n\treturn nil\n}\n\n// hasPopCover 当前页面是否还有挡人的浮层。","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/xiaohongshu/publish.go#L64-L100","documentation":"Publish uploads the provided image files into the creator publish page via uploadImages (typically rod's file-input setting plus DOM interaction). '小红书上传图片失败' wraps any error from that step: the upload button/input selector wasn't found, a file path doesn't exist or is unreadable, the file dialog/set-files CDP call failed, or the upload stalled. It does not cover later submit failures (those get '小红书发布失败').","triggerScenarios":"content.ImagePaths contains a path that doesn't exist or isn't readable by the browser process; ImagePaths is empty (though that's caught earlier with '图片不能为空'); the upload input selector no longer matches after a site change; the images exceed xiaohongshu's size/count/format limits so the page rejects them; the 300s timeout expires on slow uploads of large files.","commonSituations":"Passing relative paths when the browser runs in a different working directory or container (headless server); HEIC/oversized images the site refuses; more images than the site's per-post limit; a site redesign moving the upload input so SetFiles can't find it.","solutions":["Validate every path before calling Publish: os.Stat each entry in ImagePaths and convert to absolute paths","Check the wrapped inner error printed by errors.Wrap to see whether it's a selector miss (fix selector) or a file error (fix path)","Convert images to JPEG/PNG under the site's size limit (typically ~32MB each, ≤18 images per post)","Re-run with a longer ctx budget if uploading many large images; Publish resets to a 300s timeout","If a selector miss, dump the upload area HTML and update the input selector in uploadImages"],"exampleFix":"// before\nerr := action.Publish(ctx, content) // ImagePaths: [\"./pic/a.jpg\"] relative path\n// after\nfor i, p := range content.ImagePaths {\n    abs, err := filepath.Abs(p)\n    if err != nil || !fileExists(abs) { return fmt.Errorf(\"image not found: %s\", p) }\n    content.ImagePaths[i] = abs\n}\nerr := action.Publish(ctx, content)","handlingStrategy":"validation","validationCode":"func validateImagePaths(paths []string) error {\n    if len(paths) == 0 { return errors.New(\"images required\") }\n    for _, p := range paths {\n        abs, err := filepath.Abs(p)\n        if err != nil { return err }\n        info, err := os.Stat(abs)\n        if err != nil { return fmt.Errorf(\"image missing: %s\", p) }\n        if info.Size() > 32<<20 { return fmt.Errorf(\"image too large: %s\", p) }\n    }\n    return nil\n}","typeGuard":"func allImagesReadable(paths []string) bool {\n    for _, p := range paths {\n        f, err := os.Open(p)\n        if err != nil { return false }\n        f.Close()\n    }\n    return len(paths) > 0\n}","tryCatchPattern":"if err := action.Publish(ctx, content); err != nil {\n    if strings.Contains(err.Error(), \"小红书上传图片失败\") {\n        // inspect wrapped cause: file path vs selector vs timeout\n        log.Printf(\"upload failed: %+v\", err)\n    }\n    return err\n}","preventionTips":["Pre-validate every ImagePaths entry with os.Stat and filepath.Abs before Publish","Use absolute paths — the browser process may have a different working directory than your Go program","Keep images within the site's limits (JPEG/PNG, ~32MB, ≤18 images) and pre-convert otherwise","Give Publish a generous ctx; the 300s internal timeout also needs the ctx budget to cover large uploads"],"tags":["go","rod","upload","file-io","validation"],"backgroundTag":"file-upload-failed","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}