jackwener/OpenCLI · error · AuthRequiredError
${batch.error}
Error message
${batch.error} What it means
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.
Source
Thrown at clis/linkedin/search.js:278
credentials: 'include',
headers: { 'csrf-token': ${JSON.stringify(csrf)}, 'x-restli-protocol-version': '2.0.0' },
});
if (res.status === 401 || res.status === 403) {
const text = await res.text();
return {
authRequired: true,
error: 'LinkedIn API authentication failed: HTTP ' + res.status + ' ' + text.slice(0, 200)
};
}
if (!res.ok) {
const text = await res.text();
return { error: 'LinkedIn API error: HTTP ' + res.status + ' ' + text.slice(0, 200) };
}
return res.json();
})()`);
if (!batch || batch.error) {
if (batch?.authRequired) {
throw new AuthRequiredError(LINKEDIN_DOMAIN, batch.error);
}
throw new CommandExecutionError(batch?.error || 'LinkedIn search returned an unexpected response');
}
const elements = Array.isArray(batch?.elements) ? batch.elements : [];
if (elements.length === 0)
break;
for (const element of elements) {
const card = element?.jobCardUnion?.jobPostingCard;
if (!card)
continue;
// Extract job ID from URN fields
const jobId = [card.jobPostingUrn, card.jobPosting?.entityUrn, card.entityUrn]
.filter(Boolean)
.map(s => String(s).match(/(\d+)/)?.[1])
.find(Boolean) ?? '';
// Extract listed date
const listedItem = (card.footerItems || []).find((i) => i?.type === 'LISTED_DATE' && i?.timeAt);
const listed = listedItem?.timeAt ? new Date(listedItem.timeAt).toISOString().slice(0, 10) : '';View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before (tight loop triggers 429)
for (const q of queries) await search(q, { limit: 100 });
// after — throttle and back off
for (const q of queries) {
try { await search(q, { limit: 25 }); }
catch (e) { if (/HTTP 429/.test(e.message)) await sleep(60000); }
await sleep(10000);
} Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
const isVoyagerHttpError = (e) => e instanceof Error && /LinkedIn API error: HTTP \d{3}/.test(e.message); Try / catch
const withBackoff = async (fn, tries = 3) => {
for (let i = 0; ; i++) {
try { return await fn(); }
catch (e) {
const m = e.message.match(/HTTP (\d{3})/);
const status = m ? Number(m[1]) : 0;
if (i < tries - 1 && (status === 429 || status >= 500)) {
await new Promise(r => setTimeout(r, (status === 429 ? 60000 : 5000) * 2 ** i));
continue;
}
throw e;
}
}
};
const jobs = await withBackoff(() => run(['linkedin', 'search', 'nodejs', '--limit', '25'])); Prevention
- 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.
When it happens
Trigger: 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> ...'.
Common situations: 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.
Related errors
- API_ERROR
- API_ERROR
- HTTP ${result.httpStatus} from /api/user
- LinkedIn search returned an unexpected response
- Suno generate failed (HTTP ${status}): ${detail}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/619621cd43a5aa22.
Report an issue: GitHub.