DIYgod/RSSHub · error · Error

Failed to fetch thread data

Error message

Failed to fetch thread data

What it means

The Threads route scrapes the profile HTML, parses each <script data-sjs> blob as JSON, and runs a JSONPath for $..thread_items[0]. If none of the script blobs yield thread_items, threadsData stays null and the route throws 'Failed to fetch thread data'. The cause is upstream: Threads did not embed the expected data (markup changed, login wall, rate limit, or empty/private profile).

Source

Thrown at lib/routes/threads/index.ts:96

    let threadsData: ThreadItem[] | null = null;
    for (const el of document.querySelectorAll('script[data-sjs]')) {
        try {
            const data = JSONPath({
                path: '$..thread_items[0]',
                json: JSON.parse(el.textContent || ''),
            });

            if (data?.length > 0) {
                threadsData = data as ThreadItem[];
                break;
            }
        } catch {
            // Skip invalid JSON
        }
    }

    if (!threadsData) {
        throw new Error('Failed to fetch thread data');
    }

    debugJson.profileId = userId;
    debugJson.response = { response: threadsData };

    const userData: ThreadUser = threadsData[0]?.post?.user || { username: user, profile_pic_url: '' };

    const items = threadsData
        .filter((item) => user === item.post.user?.username)
        .map((item) => ({
            author: user,
            title: buildContent(item, options).title,
            description: buildContent(item, options).description,
            pubDate: parseDate(item.post.taken_at, 'X'),
            link: threadUrl(item.post.code),
        }));

    debugJson.items = items;

View on GitHub (pinned to bed535e087)

Solutions

  1. Open profileUrl(user) in a browser with the same UA and confirm thread_items is present in the data-sjs scripts; if not, update the JSONPath to the new location.
  2. If a login/consent page is served, rotate the egress IP or adjust headers to look less like a scraper.
  3. Confirm the username is correct and the profile is public and non-empty.
  4. Add a logged slice of the response when threadsData stays null so future markup changes are diagnosable.

Example fix

// before
if (!threadsData) {
    throw new Error('Failed to fetch thread data');
}

// after: include diagnostic context
if (!threadsData) {
    const scriptCount = document.querySelectorAll('script[data-sjs]').length;
    const looksLikeLogin = /login|challenge/i.test(response);
    throw new Error(`Failed to fetch thread data for "${user}" (data-sjs scripts: ${scriptCount}, login-wall suspected: ${looksLikeLogin})`);
}
Defensive patterns

Strategy: retry

Validate before calling

function profileEmbedsThreads(html: string): boolean {
    return /data-sjs/.test(html) && /thread_items/.test(html);
}
// fetch the profile HTML first; if !profileEmbedsThreads(html), back off or rotate IP before parsing

Type guard

function profileHtmlHasThreadData(html: string): boolean {
    return /<script[^>]*data-sjs/.test(html) && /thread_items/.test(html);
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
    try {
        return await buildThreadsFeed(user);
    } catch (e) {
        if (e instanceof Error && /Failed to fetch thread data/.test(e.message)) {
            await sleep(10_000 * (attempt + 1)); // likely rate-limit/markup drift
            continue;
        }
        throw e;
    }
}
throw new Error(`Threads feed unavailable for "${user}" after retries`);

Prevention

When it happens

Trigger: The Threads profile page no longer embeds thread_items in a data-sjs script; the request was served a login/consent page instead of the SPA shell; the profile is private or has no threads; the JSONPath expression no longer matches the new data shape.

Common situations: Threads changes its embedded JSON structure (frequent for anti-scraping sites); the hardcoded iOS-Safari User-Agent gets blocked; rate-limiting after polling; profile handle typo.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/d2931202e35365fa. Report an issue: GitHub.