{"record":{"id":"3b2101d846d97e82","repo":"jackwener/OpenCLI","slug":"lesswrong-api-returned-http-resp-status","errorCode":null,"errorMessage":"LessWrong API returned HTTP ${resp.status}","messagePattern":"LessWrong API returned HTTP (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/lesswrong/_helpers.js","lineNumber":17,"sourceCode":"import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';\nexport const SITE = 'lesswrong';\nexport const DOMAIN = 'www.lesswrong.com';\nconst GRAPHQL_URL = `https://${DOMAIN}/graphql`;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- GraphQL responses vary per query\nexport async function gqlRequest(query) {\n    const resp = await fetch(GRAPHQL_URL, {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application/json',\n            Accept: 'application/json',\n        },\n        body: JSON.stringify({ query }),\n        signal: AbortSignal.timeout(15000),\n    });\n    if (!resp.ok) {\n        throw new CommandExecutionError(`LessWrong API returned HTTP ${resp.status}`);\n    }\n    const json = (await resp.json());\n    if (json.errors?.length) {\n        throw new CommandExecutionError(json.errors[0]?.message ?? 'Unknown GraphQL error');\n    }\n    return json.data;\n}\nexport function gqlEscape(str) {\n    return str.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\nexport function stripHtml(html) {\n    if (!html)\n        return '';\n    return html\n        .replace(/<script[^>]*>.*?<\\/script>/gis, ' ')\n        .replace(/<style[^>]*>.*?<\\/style>/gis, ' ')\n        .replace(/<[^>]+>/g, ' ')\n        .replace(/\\s+/g, ' ')","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/lesswrong/_helpers.js#L1-L35","documentation":"gqlRequest calls the LessWrong GraphQL HTTP endpoint and requires a 2xx response. Any non-OK HTTP status (403, 429, 5xx, etc.) throws CommandExecutionError with the status code. It surfaces transport-level failures before GraphQL error handling.","triggerScenarios":"LessWrong returns HTTP 4xx/5xx for the POSTed GraphQL query: rate limiting (429), Cloudflare block (403), server error (500/502/503), or a malformed request causing 400.","commonSituations":"Hammering the API with many rapid queries triggers rate limiting; corporate proxy or VPN blocked by Cloudflare; LessWrong downtime during deploys; query too large or malformed leading to 400.","solutions":["Retry the request after a delay, honoring backoff if status was 429","Check https://www.lesswrong.com status / try the query in a browser to confirm the service is up","Reduce request frequency or batch queries to avoid rate limits","Verify network path (proxy/VPN) is not causing Cloudflare 403 blocks"],"exampleFix":"// before\nconst data = await gqlRequest(query);\n// after\nconst data = await withRetry(() => gqlRequest(query), { retries: 3, backoffMs: 1000 });","handlingStrategy":"retry","validationCode":"// pre-flight\nconst ping = await fetch('https://www.lesswrong.com', { method: 'HEAD' });\nif (!ping.ok) console.warn('LessWrong unreachable, expect HTTP errors');","typeGuard":null,"tryCatchPattern":"try {\n  const data = await gqlRequest(query);\n} catch (e) {\n  const m = /HTTP (\\d+)/.exec(String(e.message));\n  if (m) {\n    const status = Number(m[1]);\n    if (status === 429 || status >= 500) await sleep(backoff); /* retry */\n  } else throw e;\n}","preventionTips":["Add exponential backoff with retry on 429/5xx","Space out LessWrong queries to avoid rate limits","Monitor lesswrong.com status before bulk runs","Avoid suspicious IPs/VPNs that trigger Cloudflare 403"],"tags":["http","graphql","network","lesswrong"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}