{"record":{"id":"1f220b3aa4afac88","repo":"jackwener/OpenCLI","slug":"no-data-1f220b","errorCode":"NO_DATA","errorMessage":"Could not retrieve Product Hunt top posts","messagePattern":"Could not retrieve Product Hunt top posts","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/producthunt/hot.js","lineNumber":91,"sourceCode":"            })\n            .filter((candidate) => /^\\\\d+$/.test(candidate.text));\n\n          if (voteCandidates.length === 0) continue;\n\n          seen.add(href);\n          results.push({\n            name,\n            voteCandidates,\n            url: 'https://www.producthunt.com' + href,\n          });\n        }\n\n        return results;\n      })()\n    `);\n        const items = Array.isArray(domItems) ? domItems : [];\n        if (items.length === 0) {\n            throw new CliError('NO_DATA', 'Could not retrieve Product Hunt top posts', 'Product Hunt may have changed its layout');\n        }\n        const rankedItems = items\n            .map((item) => ({\n            name: item.name,\n            url: item.url,\n            votes: pickVoteCount(Array.isArray(item.voteCandidates) ? item.voteCandidates : []),\n        }))\n            .filter((item) => item.name && item.url && item.votes);\n        if (rankedItems.length === 0) {\n            throw new CliError('NO_DATA', 'Could not retrieve Product Hunt vote counts', 'Product Hunt may have changed its vote button structure');\n        }\n        rankedItems.sort((a, b) => parseInt(b.votes, 10) - parseInt(a.votes, 10));\n        return rankedItems.slice(0, count).map((item, i) => ({\n            rank: i + 1,\n            name: item.name,\n            votes: item.votes,\n            url: item.url,\n        }));","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/producthunt/hot.js#L73-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Open https://www.producthunt.com in a browser and confirm a[href^=\"/products/\"] cards still exist; update selectors if the layout changed","Re-run with a longer wait (increase waitForCapture timeout) to rule out slow rendering","Check whether Product Hunt is serving a captcha/consent page to your IP; use a different network or add consent-cookie handling","Verify installInterceptor is capturing the expected requests; inspect captured payloads for the feed data","Retry later — transient bot-blocking or partial outage can produce an empty DOM"],"exampleFix":"// before\nconst items = Array.isArray(domItems) ? domItems : [];\nif (items.length === 0) {\n    throw new CliError('NO_DATA', 'Could not retrieve Product Hunt top posts', 'Product Hunt may have changed its layout');\n}\n// after\nconst items = Array.isArray(domItems) ? domItems : [];\nif (items.length === 0) {\n    // give the SPA more time, then retry once before failing\n    await page.waitForTimeout(3000);\n    const retryItems = Array.isArray(await page.evaluate(SCRAPE_SNIPPET)) ? domItems : [];\n    if (retryItems.length === 0) {\n        throw new CliError('NO_DATA', 'Could not retrieve Product Hunt top posts', 'Product Hunt may have changed its layout');\n    }\n}","handlingStrategy":"try-catch","validationCode":"// preflight: confirm the page renders product cards before running the command\nconst cardCount = await page.evaluate(`document.querySelectorAll('a[href^=\"/products/\"]').length`);\nif (!cardCount) throw new Error('Product Hunt page rendered no product cards — likely blocked or redesigned');","typeGuard":"function isDomItemArray(v) {\n  return Array.isArray(v) && v.every((i) => i && typeof i.name === 'string' && typeof i.url === 'string');\n}","tryCatchPattern":"try {\n  const posts = await runCli('producthunt hot', { limit: 20 });\n} catch (e) {\n  if (e?.code === 'NO_DATA') {\n    console.warn('Product Hunt layout may have changed; retrying with longer wait or falling back to cached data.');\n    return cachedPosts ?? [];\n  }\n  throw e;\n}","preventionTips":["Pin/update selectors after each Product Hunt redesign; add a smoke test asserting >0 cards","Increase waitForCapture timeout for slow/headless environments","Handle consent walls and bot challenges before scraping","Fall back to intercepted network payloads when DOM scraping returns empty"],"tags":["scraping","dom","no-data","cli"],"backgroundTag":"empty-scrape-result","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}