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

  1. Wait and retry with backoff — most commonly this is HTTP 429 rate limiting from issuing searches too frequently.
  2. Reduce --limit (each page of 25 is one API call) and avoid tight pagination loops.
  3. Re-run once after re-loading the LinkedIn page; transient 5xx errors often clear immediately.
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/619621cd43a5aa22. Report an issue: GitHub.