{"record":{"id":"5f3a1b711ef3db84","repo":"xpzouying/xiaohongshu-mcp","slug":"error-5f3a1b","errorCode":null,"errorMessage":"读不到回复框提示文字，无法确认回复对象，已中止","messagePattern":"读不到回复框提示文字，无法确认回复对象，已中止","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"xiaohongshu/notification_reply.go","lineNumber":142,"sourceCode":"\t\t}\n\n\t\tif err := page.Mouse.Scroll(0, 800, 5); err != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"滚动查找失败: %w\", err)\n\t\t}\n\t\thumanize.Delay(ctx, humanize.BetweenScroll)\n\n\t\tif err := ctx.Err(); err != nil {\n\t\t\treturn nil, 0, err\n\t\t}\n\t}\n\treturn nil, 0, fmt.Errorf(\"翻找 %d 轮仍未定位到评论 %s\", maxRounds, commentID)\n}\n\n// verifyReplyTarget 用输入框的 placeholder 核对回复对象。\nfunc verifyReplyTarget(input *rod.Element, nickname string) error {\n\tplaceholder, err := input.Attribute(\"placeholder\")\n\tif err != nil || placeholder == nil {\n\t\treturn fmt.Errorf(\"读不到回复框提示文字，无法确认回复对象，已中止\")\n\t}\n\tif nickname != \"\" && !strings.Contains(*placeholder, nickname) {\n\t\treturn fmt.Errorf(\"回复对象不符：期望 %q，实际提示为 %q，已中止\", nickname, *placeholder)\n\t}\n\treturn nil\n}\n\n// waitReplyAccepted 等待回复提交完成。\nfunc waitReplyAccepted(item *rod.Element, timeout time.Duration) error {\n\tdeadline := time.Now().Add(timeout)\n\tfor time.Now().Before(deadline) {\n\t\thas, _, err := item.Has(`textarea.comment-input`)\n\t\tif err == nil && !has {\n\t\t\treturn nil\n\t\t}\n\t\ttime.Sleep(300 * time.Millisecond)\n\t}\n\treturn fmt.Errorf(\"回复未确认成功：发送后输入框仍未收起（可能被限制或发送失败）\")","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/xiaohongshu/notification_reply.go#L124-L160","documentation":"verifyReplyTarget() reads the reply textarea's placeholder attribute to confirm the reply targets the expected nickname. If rod cannot read the attribute at all (err != nil) or the attribute is absent (nil), it aborts rather than risk replying to the wrong person. This is a safety stop — the library refuses to proceed without identity confirmation.","triggerScenarios":"Calling Reply when the comment-input textarea has no placeholder attribute (XHS UI change, locale rendering variant, or the input element was captured before the panel finished rendering), or rod's Attribute call fails due to a stale/detached element.","commonSituations":"A Xiaohongshu frontend update renaming/removing the placeholder attribute; page in a different state where the input exists but is not hydrated; grabbing the element from the wrong item index; detached element after a re-render between click and attribute read.","solutions":["Check whether XHS changed the reply-input DOM (inspect textarea.comment-input for placeholder vs aria-label / data-* attributes) and update the selector/attribute used","Re-fetch the element immediately before reading the attribute to avoid a detached node","Add a short wait for the input's attributes to hydrate before verifying","If XHS removed placeholders permanently, verify identity via the adjacent user-name element instead"],"exampleFix":"// before\nplaceholder, err := input.Attribute(\"placeholder\")\nif err != nil || placeholder == nil { return err }\n// after\nph, err := input.Attribute(\"placeholder\")\nif err != nil || ph == nil {\n    ph, err = input.Attribute(\"aria-label\") // fallback attr after XHS UI change\n    if err != nil || ph == nil { return err }\n}","handlingStrategy":"try-catch","validationCode":"// pre-check the input element exists and has identification attributes before Reply\nitem := items[index]\ninput, err := item.Element(`textarea.comment-input`)\nif err != nil { return fmt.Errorf(\"reply input missing: %w\", err) }\nph, _ := input.Attribute(\"placeholder\")\nlabel, _ := input.Attribute(\"aria-label\")\nif ph == nil && label == nil { return fmt.Errorf(\"input has no identifying attributes; abort\") }","typeGuard":null,"tryCatchPattern":"if _, err := action.Reply(ctx, commentID, content); err != nil {\n    if strings.Contains(err.Error(), \"读不到回复框提示文字\") {\n        log.Printf(\"cannot verify reply target (UI change?) for %s — skipping, NOT retrying blind\", commentID)\n        return nil // treat as aborted-by-safety-check\n    }\n    return err\n}","preventionTips":["Never disable or bypass verifyReplyTarget — it prevents wrong-thread replies","After XHS frontend deploys, smoke-test one reply before batch runs","Keep the placeholder/aria-label selector logic in one place for quick updates"],"tags":["browser-automation","go","dom","ui-change"],"backgroundTag":"attribute-read-failed","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}