jackwener/OpenCLI · error · CliError

NO_DATA

NO_DATA

Error message

Could not retrieve Product Hunt top posts

What it means

CliError('NO_DATA') thrown by clis/producthunt/hot.js:91 when the DOM scraping script inside page.evaluate finds zero product cards on the Product Hunt homepage. The command relies on an INTERCEPT strategy plus DOM scraping of a[href^="/products/"] links, so any change to Product Hunt's markup (or a failed/blocked page load) yields an empty items array and this error. It signals that the CLI could not extract top posts, not that the network call itself failed.

Source

Thrown at clis/producthunt/hot.js:91

            })
            .filter((candidate) => /^\\d+$/.test(candidate.text));

          if (voteCandidates.length === 0) continue;

          seen.add(href);
          results.push({
            name,
            voteCandidates,
            url: 'https://www.producthunt.com' + href,
          });
        }

        return results;
      })()
    `);
        const items = Array.isArray(domItems) ? domItems : [];
        if (items.length === 0) {
            throw new CliError('NO_DATA', 'Could not retrieve Product Hunt top posts', 'Product Hunt may have changed its layout');
        }
        const rankedItems = items
            .map((item) => ({
            name: item.name,
            url: item.url,
            votes: pickVoteCount(Array.isArray(item.voteCandidates) ? item.voteCandidates : []),
        }))
            .filter((item) => item.name && item.url && item.votes);
        if (rankedItems.length === 0) {
            throw new CliError('NO_DATA', 'Could not retrieve Product Hunt vote counts', 'Product Hunt may have changed its vote button structure');
        }
        rankedItems.sort((a, b) => parseInt(b.votes, 10) - parseInt(a.votes, 10));
        return rankedItems.slice(0, count).map((item, i) => ({
            rank: i + 1,
            name: item.name,
            votes: item.votes,
            url: item.url,
        }));

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open https://www.producthunt.com in a browser and confirm a[href^="/products/"] cards still exist; update selectors if the layout changed
  2. Re-run with a longer wait (increase waitForCapture timeout) to rule out slow rendering
  3. Check whether Product Hunt is serving a captcha/consent page to your IP; use a different network or add consent-cookie handling
  4. Verify installInterceptor is capturing the expected requests; inspect captured payloads for the feed data
  5. Retry later — transient bot-blocking or partial outage can produce an empty DOM

Example fix

// before
const items = Array.isArray(domItems) ? domItems : [];
if (items.length === 0) {
    throw new CliError('NO_DATA', 'Could not retrieve Product Hunt top posts', 'Product Hunt may have changed its layout');
}
// after
const items = Array.isArray(domItems) ? domItems : [];
if (items.length === 0) {
    // give the SPA more time, then retry once before failing
    await page.waitForTimeout(3000);
    const retryItems = Array.isArray(await page.evaluate(SCRAPE_SNIPPET)) ? domItems : [];
    if (retryItems.length === 0) {
        throw new CliError('NO_DATA', 'Could not retrieve Product Hunt top posts', 'Product Hunt may have changed its layout');
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight: confirm the page renders product cards before running the command
const cardCount = await page.evaluate(`document.querySelectorAll('a[href^="/products/"]').length`);
if (!cardCount) throw new Error('Product Hunt page rendered no product cards — likely blocked or redesigned');

Type guard

function isDomItemArray(v) {
  return Array.isArray(v) && v.every((i) => i && typeof i.name === 'string' && typeof i.url === 'string');
}

Try / catch

try {
  const posts = await runCli('producthunt hot', { limit: 20 });
} catch (e) {
  if (e?.code === 'NO_DATA') {
    console.warn('Product Hunt layout may have changed; retrying with longer wait or falling back to cached data.');
    return cachedPosts ?? [];
  }
  throw e;
}

Prevention

When it happens

Trigger: page.evaluate returns [] or non-array: (a) page.goto loaded a captcha/login/blocked page instead of the homepage, (b) Product Hunt renamed or removed the a[href^="/products/"] card structure or added /reviews filter that excludes all links, (c) waitForCapture(5) expired before cards rendered, (d) the interceptor captured nothing and the SPA never rendered product cards.

Common situations: Product Hunt ships a redesign or A/B test changing product-card markup; running in a region/IP that gets a bot-challenge page; slow network or headless environment where hydration hasn't finished in 5 seconds; scraping from CI where the homepage serves a consent wall.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/1f220b3aa4afac88. Report an issue: GitHub.