feat: capture bookmark reply threads#141
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 840c9de. Configure here.
| let below: ThreadTweetSnapshot[] = []; | ||
| if (cookies.csrfToken) { | ||
| const detail = await fetchTweetDetailViaGraphQL(record.tweetId, cookies.csrfToken, cookies.cookieHeader, { delayMs }); | ||
| if (detail.status !== 'ok') return { context, below: [], status: detail.status }; |
There was a problem hiding this comment.
TweetDetail non-ok status discards fetched parent context
Medium Severity
When fetchTweetDetailViaGraphQL returns a non-ok status (especially 'empty', meaning the timeline was recognized but had no tweet results), expandThreadForRecord propagates that status even though parent context tweets were already successfully fetched. In syncThreads, the non-ok status causes the entire expansion to be treated as failed — the already-fetched context array in expanded.context is never written to the record. For 'empty' specifically — which just means "no continuation below" — this is classified as a permanent failure, stamping threadExpansionFailedAt and preventing any future retry. The successfully-fetched parent context is permanently lost.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 840c9de. Configure here.
| url: record.url, | ||
| }); | ||
| await persistProgress(); | ||
| throw err; |
There was a problem hiding this comment.
Unreachable error-handling code in syncThreads catch block
Low Severity
The failed++ and failures.push(...) after the if (THREAD_TRANSIENT_FAILURE_STATUSES.has(status)) guard in the catch block is dead code. The default status is 'error', which is a member of THREAD_TRANSIENT_FAILURE_STATUSES. Any throw from the inner try block either sets status to a transient value (and re-throws), or falls through without throwing. Any exception from the fetcher itself leaves status at 'error'. In all cases, the guard is true and the function re-throws before reaching the unreachable lines.
Reviewed by Cursor Bugbot for commit 840c9de. Configure here.
|
You have used all of your free Bugbot PR reviews. To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial. |


Summary
ft sync --threadsto capture parent reply context and same-author continuations for bookmarked X postsWhy
X threads increasingly carry the paper, tool, model, or app links in replies to avoid link penalties on the primary post. Bookmark search needs to retain and search that reply context, not just the root post.
Notes
src/tweet-snapshots.tsto keepgraphql-bookmarks.tsfrom growing another large endpoint-specific parser block.thread_text, backfilling it from stored thread JSON, and rebuilding FTS.Validation
npm run buildnpm testgit diff --checkNote
Medium Risk
Adds a new GraphQL-based thread-expansion sync path plus DB schema/FTS migrations; correctness and rate-limit handling depend on X endpoint stability and migration/backfill behavior.
Overview
Adds optional
ft sync --threadssupport to capture reply-thread context (parent chain) and same-author continuations below bookmarked posts, persisting this data to both JSONL cache and the SQLite index.Extends the DB schema and FTS indexing to store
threadContext/threadBelow, compute a searchablethread_textfield (with migration backfill + conditional FTS rebuild), and updates gap-fill to also refreshlinkswhen text is expanded.Updates media fetching and CLI output to include thread tweet media/profile images and display thread sections, and factors TweetDetail parsing/link expansion into a new
tweet-snapshots.tsutility used by the new thread sync flow.Reviewed by Cursor Bugbot for commit 840c9de. Bugbot is set up for automated code reviews on this repo. Configure here.