jackwener / jackwener/OpenCLI

xiaohongshu: persistent site session, navigation pacing, and risk-control hardening (umbrella for #2460 / #2461 / #2464)

Open
#2,470 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
29.3k
Forks
2.9k
Avg merge
15h 36m
Merged PRs (30d)
70

Description

## Motivation

Batch usage of the xiaohongshu adapter (search → note details → comments rounds) amplifies navigations: every command was ephemeral, so each invocation opened a fresh tab, loaded a page, and closed the tab — a traffic pattern no human produces, and exactly what `sitemaps/xiaohongshu/pitfalls.md` documents as the trigger for velocity-based risk control (`安全限制`, error_code 300017/300031, escalation toward account restrictions per #842/#677). The pitfalls doc prescribes pacing rules ("1-2s between requests", "60s cooldown after a block") but nothing enforced them.

Three PRs address this as one arc: make the adapter behave like a single logged-in user in one tab (#2461), enforce the pacing rules centrally instead of hoping callers follow them (#2464), and fix search which was fully broken on the current layout (#2460).

## What each PR does and why

**#2460 — search filter fix.** The live `search_result` layout renders every filter chip twice, stacked at pixel-identical positions; `findOption`'s exactly-one-visible-match contract made every search die with `ambiguous_option` before any filter applied. Pixel-identical duplicates are now treated as one control; genuinely distinct positions still fail closed.

**#2461 — persistent site session, in three phases** (kept as one PR deliberately). Phase 1: the eight self-guarding commands (`note`/`comments`/`download`/`ask`/`creator-profile`/`creator-stats`/`follow`/`unfollow`) — declaration-only, since their navigation targets are parameterized or fast-path-safe. Phase 2: the four page-state readers (`feed`/`user`/`saved`/`liked`) — each converted **with** its staleness hazard solved (`navigateFresh` forced reloads, landed-page verification, hydration polling). Phase 3: `search`, after verifying in `extension/src/background.ts` that its `replaceCollapsedTab` recovery rebinds the tab lease before closing the old tab. A convention test asserts the boundary in both directions: 13 commands must be persistent, and `publish`/`delete-note`/`draft-*` (dirty-composer hazard) plus the creator capture trio (navigation-triggered signed x-s XHRs) must stay ephemeral until those hazards are solved.

**#2464 — daemon-side navigation pacing + circuit breaker.** The daemon is the one process that sees every CLI client (the session-lease argument), so it spaces adapter navigations per (contextId, site) with jitter (xiaohongshu 1.5–3s, weibo 1–2s) and opens a randomized 5–10min cooldown after 2 SECURITY_BLOCK outcomes in 10min, failing fast with machine-readable `security_cooldown` + `retryAfterMs`. Fewer, human-paced page loads are both the performance win and the de-flagging win — they are the same thing.

## Bugs found and fixed along the way

- The soft-block retry in `readXhsDetailPage` never actually reloaded for the in-page block variant — the extension fast-paths a goto to the tab's current URL, so the "retry" re-read the same blocked document (pre-existing; #2461).
- The `saved`/`liked` XHR interceptor was installed before the goto, so navigation wiped the in-page patch and the capture path never captured anything — the DOM fallback silently did all the work (pre-existing; #2461).
- **Parallel reads on the shared persistent tab silently returned the wrong note** — found by merging all three PRs on an integration branch and racing two `note` commands: one returned the *other* note's content with a success exit. Detail reads now verify the landed note id / profile pathname, re-navigate once, then fail typed `TAB_CONTENTION` (#2461).
- `resolveXhsUserId` raced `__INITIAL_STATE__` hydration after a forced reload and reported a logged-in user as logged out — caught live, fixed with polling-before-reload (#2461).

## Verification

Every change TDD'd (≈90 new unit tests across the three PRs; convention tests lock declarations and the phase boundary). Each PR verified independently (full suite + typecheck + both lint gates green), plus a local integration branch combining all three: combined suite 7382 green, the repo's own e2e suite 105/105 against real Chrome, and a live sweep — warm commands 0.7–3.4s vs 3.6–7.5s ephemeral, feed returning fresh content per call, pacing staircase visible in daemon `/logs`, and the parallel-race test ending in correct-content + typed-failure instead of silent corruption.

## Open questions / follow-ups

- **Read leases**: the losing side of a parallel same-site read now fails loudly (`TAB_CONTENTION` or a transport detach), but graceful arbitration would need framework-level read leases — a design decision beyond these PRs.
- **Login coverage gap**: `xiaohongshu login` establishes the `creator.xiaohongshu.com` session only; `www.xiaohongshu.com` needs its own `web_session` (search is the only www command that hard-requires it), while the sitemap doc claims shared SSO. Worth a separate fix or doc correction.
- Same conversion pattern planned next for `bilibili` (19 root-prenav commands) and `twitter` (18).

**Since filed:** two live-room features grew out of this arc's groundwork — #2471 (`lives`: room discovery with directly playable FLV stream URLs) and #2472 (`live-comments` / `live-comment-send`: DOM-sourced comment stream capture and posting). The xsec_source fix originally opened as #2474 was folded into #2461.

Related: #2460, #2461, #2464, #2471, #2472 (and the weibo counterpart #2442 that started the series).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Contributor guide

Open the contributing guide

Research direction

This umbrella issue is already split across #2460, #2461, and #2464, with related follow-ups #2471 and #2472. Read those PRs first, then inspect sitemaps/xiaohongshu/pitfalls.md, extension/src/background.ts, the adapter commands, and the daemon navigation path. The listed verification covers unit tests, typecheck, lint, e2e, pacing logs, and parallel-read behavior; remaining work is framed as follow-up design or documentation questions.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, playwright
Domain
backend, cli, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
15/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.