{"record":{"id":"619621cd43a5aa22","repo":"jackwener/OpenCLI","slug":"batch-error","errorCode":null,"errorMessage":"${batch.error}","messagePattern":"\\$\\{batch\\.error\\}","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/search.js","lineNumber":278,"sourceCode":"        credentials: 'include',\n        headers: { 'csrf-token': ${JSON.stringify(csrf)}, 'x-restli-protocol-version': '2.0.0' },\n      });\n      if (res.status === 401 || res.status === 403) {\n        const text = await res.text();\n        return {\n          authRequired: true,\n          error: 'LinkedIn API authentication failed: HTTP ' + res.status + ' ' + text.slice(0, 200)\n        };\n      }\n      if (!res.ok) {\n        const text = await res.text();\n        return { error: 'LinkedIn API error: HTTP ' + res.status + ' ' + text.slice(0, 200) };\n      }\n      return res.json();\n    })()`);\n        if (!batch || batch.error) {\n            if (batch?.authRequired) {\n                throw new AuthRequiredError(LINKEDIN_DOMAIN, batch.error);\n            }\n            throw new CommandExecutionError(batch?.error || 'LinkedIn search returned an unexpected response');\n        }\n        const elements = Array.isArray(batch?.elements) ? batch.elements : [];\n        if (elements.length === 0)\n            break;\n        for (const element of elements) {\n            const card = element?.jobCardUnion?.jobPostingCard;\n            if (!card)\n                continue;\n            // Extract job ID from URN fields\n            const jobId = [card.jobPostingUrn, card.jobPosting?.entityUrn, card.entityUrn]\n                .filter(Boolean)\n                .map(s => String(s).match(/(\\d+)/)?.[1])\n                .find(Boolean) ?? '';\n            // Extract listed date\n            const listedItem = (card.footerItems || []).find((i) => i?.type === 'LISTED_DATE' && i?.timeAt);\n            const listed = listedItem?.timeAt ? new Date(listedItem.timeAt).toISOString().slice(0, 10) : '';","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/search.js#L260-L296","documentation":"The in-page fetch to LinkedIn's Voyager jobs API returned a non-OK, non-401/403 status (e.g. 429 or 5xx). The page-context script wraps the status and first 200 chars of the body into batch.error, and fetchJobCards surfaces it as a CommandExecutionError with that exact message.","triggerScenarios":"Calling `opencli linkedin search` while the Voyager endpoint /voyager/api/voyagerJobsDashJobCards responds with HTTP status other than 200/401/403 — typically 429 rate limiting or 5xx server errors — producing batch.error = 'LinkedIn API error: HTTP <status> ...'.","commonSituations":"Rapid repeated searches hitting LinkedIn rate limits (HTTP 429); LinkedIn transient server errors (5xx); LinkedIn returning an unexpected response because of bot-detection interstitials; CSRF token accepted but request throttled due to pagination loops with large --limit values.","solutions":["Wait and retry with backoff — most commonly this is HTTP 429 rate limiting from issuing searches too frequently.","Reduce --limit (each page of 25 is one API call) and avoid tight pagination loops.","Re-run once after re-loading the LinkedIn page; transient 5xx errors often clear immediately.","If persistent, sign in fresh in the browser and retry — persistent non-auth errors can indicate LinkedIn is serving challenge pages; if the problem continues the Voyager decorationId or endpoint may have changed and the code needs updating."],"exampleFix":"// before (tight loop triggers 429)\nfor (const q of queries) await search(q, { limit: 100 });\n// after — throttle and back off\nfor (const q of queries) {\n  try { await search(q, { limit: 25 }); }\n  catch (e) { if (/HTTP 429/.test(e.message)) await sleep(60000); }\n  await sleep(10000);\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"const isVoyagerHttpError = (e) => e instanceof Error && /LinkedIn API error: HTTP \\d{3}/.test(e.message);","tryCatchPattern":"const withBackoff = async (fn, tries = 3) => {\n  for (let i = 0; ; i++) {\n    try { return await fn(); }\n    catch (e) {\n      const m = e.message.match(/HTTP (\\d{3})/);\n      const status = m ? Number(m[1]) : 0;\n      if (i < tries - 1 && (status === 429 || status >= 500)) {\n        await new Promise(r => setTimeout(r, (status === 429 ? 60000 : 5000) * 2 ** i));\n        continue;\n      }\n      throw e;\n    }\n  }\n};\nconst jobs = await withBackoff(() => run(['linkedin', 'search', 'nodejs', '--limit', '25']));","preventionTips":["Space out LinkedIn searches (seconds to minutes between calls) to stay under rate limits.","Keep --limit modest; each batch of 25 results is one Voyager API call.","Retry with exponential backoff on 429/5xx instead of immediately re-running.","Watch for repeated 429s as an early signal to slow down before LinkedIn escalates to blocks or checkpoints."],"tags":["linkedin","api","http-error","rate-limit","voyager"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}