jackwener/OpenCLI · error · AuthRequiredError

LinkedIn Learning auth failed (HTTP ${result.status ?? ''}).

Error message

LinkedIn Learning auth failed (HTTP ${result.status ?? ''}).

What it means

An AuthRequiredError thrown when the in-page API fetch returns HTTP 401 or 403 — buildFetchScript maps those statuses to {authRequired:true, status}. Unlike error 2275, a JSESSIONID cookie existed, but the server rejected the request: the session cookie is stale/expired or the derived csrf-token did not match, so LinkedIn's API rejected the authenticated call.

Source

Thrown at clis/linkedin-learning/shared.js:57

      return { error: 'fetch failed: ' + ((e && e.message) || String(e)) };
    }
  })()`;
}

export async function fetchLinkedInLearningApi(page, url) {
    await page.goto('https://www.linkedin.com/learning/');
    await page.wait(3);

    const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });
    const jsession = cookies.find((c) => c.name === 'JSESSIONID')?.value;
    if (!jsession) {
        throw new AuthRequiredError(DOMAIN, 'LinkedIn JSESSIONID cookie not found. Please sign in to LinkedIn in the browser.');
    }
    const csrf = jsession.replace(/^"|"$/g, '');

    const result = unwrapEvaluateResult(await page.evaluate(buildFetchScript(url, csrf)));
    if (result?.authRequired) {
        throw new AuthRequiredError(DOMAIN, `LinkedIn Learning auth failed (HTTP ${result.status ?? ''}).`);
    }
    return result;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-login in the controlled browser to refresh the session, then retry.
  2. If 403 persists, check account standing/region access to LinkedIn Learning in the web UI.
  3. Confirm the csrf-token header equals JSESSIONID with surrounding double quotes stripped.
  4. Clear linkedin.com cookies and sign in fresh (stale-cookie mismatch).
  5. Retry after completing any LinkedIn verification prompt in the browser.

Example fix

// before
const csrf = jsession.replace(/^"|"$/g, ''); // assumes only quote trimming is needed
// after
const csrf = decodeURIComponent(jsession.replace(/^"|"$/g, ''));
if (result?.authRequired && result.status === 401) throw new AuthRequiredError(DOMAIN, 'Session expired; re-login required.');
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: confirm the session is live by hitting a cheap authenticated page
await page.goto('https://www.linkedin.com/learning/');
const signedOut = await page.evaluate(() => !!document.querySelector('a[href*="/login"]'));
if (signedOut) throw new Error('LinkedIn session expired; re-login before running API commands');

Type guard

function isAuthRejection(r) { return !!r && typeof r === 'object' && r.authRequired === true && typeof r.status === 'number'; }

Try / catch

try {
  const rows = await search(page, { keywords });
} catch (e) {
  if (/auth failed \(HTTP 40[13]\)/.test(e.message)) {
    await relogin(page);      // refresh JSESSIONID, re-derive csrf
    return search(page, { keywords }); // retry once with fresh session
  }
  throw e;
}

Prevention

When it happens

Trigger: The JSESSIONID is present but expired server-side (soft logout), the csrf header derived from JSESSIONID (quotes stripped) does not match what LinkedIn expects, or the account is restricted/blocked from the learning-api endpoint (403).

Common situations: Long-lived automation profiles whose session silently expired, LinkedIn rotating session tokens after a security event, corporate/region restrictions returning 403, or clock/JAR inconsistencies after cookie export.

Related errors


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