{"record":{"id":"fc5a81b2e6f83f25","repo":"jackwener/OpenCLI","slug":"toutiao-recommend-failed-http-resp-status","errorCode":null,"errorMessage":"toutiao recommend failed: HTTP ${resp.status}","messagePattern":"toutiao recommend failed: HTTP (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/toutiao/recommend.js","lineNumber":52,"sourceCode":"    columns: ['rank', 'group_id', 'title', 'abstract', 'source', 'tag', 'comments', 'published_at', 'url', 'image_url'],\n    func: async (kwargs) => {\n        const category = parseRecommendCategory(kwargs?.category, '__all__');\n        const limit = parseRecommendLimit(kwargs?.limit, 20);\n        const url = `${RECOMMEND_URL}?category=${encodeURIComponent(category)}`;\n        let resp;\n        try {\n            resp = await fetch(url, {\n                headers: {\n                    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',\n                    Accept: 'application/json',\n                    Referer: 'https://www.toutiao.com/',\n                },\n            });\n        } catch (error) {\n            throw new CommandExecutionError(`toutiao recommend request failed: ${error?.message || error}`);\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`toutiao recommend failed: HTTP ${resp.status}`);\n        }\n        let payload;\n        try {\n            payload = await resp.json();\n        } catch (error) {\n            throw new CommandExecutionError(`toutiao recommend returned malformed JSON: ${error?.message || error}`);\n        }\n        if (payload?.message && payload.message !== 'success') {\n            throw new CommandExecutionError(`toutiao recommend returned message=${payload.message}`);\n        }\n        if (!Array.isArray(payload?.data)) {\n            throw new CommandExecutionError('toutiao recommend returned a non-array data field');\n        }\n        const rows = payload.data.map(mapRecommendRow).filter(Boolean).slice(0, limit);\n        if (rows.length === 0) {\n            throw new EmptyResultError('toutiao recommend', `频道 ${category} 返回空列表。`);\n        }\n        // Re-rank (1..N) after filter so ranks are dense even if upstream had ads.","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/toutiao/recommend.js#L34-L70","documentation":"This CommandExecutionError is thrown when the upstream Toutiao recommend endpoint responds with a non-2xx HTTP status. The status code is embedded in the message so developers can immediately distinguish auth issues (403), rate limiting (429), and server errors (5xx).","triggerScenarios":"Calling 'toutiao recommend' when resp.ok is false — e.g. upstream returns 403 (anti-bot block), 429 (rate limit), 500/502/503 (server-side failure).","commonSituations":"Toutiao's anti-scraping measures returning 403 for datacenter IPs or missing cookies; hitting the endpoint too frequently causing 429; temporary upstream outages producing 5xx.","solutions":["Read the HTTP status from the message and act accordingly: 403 → vary IP/User-Agent or add valid cookies; 429 → back off and slow request rate; 5xx → retry later.","Add exponential backoff with retries for 429/5xx before surfacing the failure.","Reduce call frequency or cache results between invocations.","Verify the endpoint URL is still valid — upstream API paths occasionally change and start returning 404."],"exampleFix":"// before\nawait recommend({ category: 'tech' });\n// after\ntry {\n  await recommend({ category: 'tech' });\n} catch (e) {\n  const m = /HTTP (\\d+)/.exec(e.message);\n  if (m && (m[1] === '429' || m[1].startsWith('5'))) {\n    await new Promise(r => setTimeout(r, 5000));\n    return recommend({ category: 'tech' });\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await recommend({ category });\n} catch (e) {\n  const m = /HTTP (\\d{3})/.exec(e.message);\n  const status = m && Number(m[1]);\n  if (status === 429 || (status >= 500 && status < 600)) {\n    await new Promise(r => setTimeout(r, 5000));\n    return recommend({ category }); // retry with backoff\n  }\n  if (status === 403) throw new Error('Blocked (403): rotate IP/UA or add cookies');\n  throw e;\n}","preventionTips":["Throttle request rate and cache results to avoid 429s.","For 403s, send realistic headers/cookies and avoid datacenter IPs.","Retry only retryable statuses (429, 5xx), not 4xx client errors.","Alert on persistent 5xx — it is an upstream outage."],"tags":["http","http-status","upstream-api","rate-limit"],"backgroundTag":"http-non-2xx-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}