jackwener/OpenCLI · error · AuthRequiredError

LinkedIn li_at cookie missing

Error message

LinkedIn li_at cookie missing

What it means

verifyLinkedinIdentity first checks the browser context for a non-empty li_at session cookie; if absent it throws AuthRequiredError('linkedin.com', ...). li_at is LinkedIn's persistent login cookie — without it every authenticated Voyager API call would 401, so the library fails fast and tells the caller to log in.

Source

Thrown at clis/linkedin/auth.js:11

import { AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
import { registerSiteAuthCommands } from '../_shared/site-auth.js';

async function hasLinkedinSessionCookie(page) {
  const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });
  return cookies.some(c => c.name === 'li_at' && c.value);
}

async function verifyLinkedinIdentity(page) {
  if (!await hasLinkedinSessionCookie(page)) {
    throw new AuthRequiredError('linkedin.com', 'LinkedIn li_at cookie missing');
  }
  await page.goto('https://www.linkedin.com/feed/');
  await page.wait(2);
  const result = await page.evaluate(`(async () => {
    try {
      const jsessionRaw = (document.cookie.split('; ').find(c => c.startsWith('JSESSIONID=')) || '').split('=')[1] || '';
      const csrf = jsessionRaw.replace(/^"|"$/g, '');
      if (!csrf) return { kind: 'auth', detail: 'LinkedIn JSESSIONID missing — csrf token unavailable' };
      const res = await fetch('/voyager/api/me', { credentials: 'include', headers: { 'csrf-token': csrf, 'Accept': 'application/json' } });
      if (res.status === 401 || res.status === 403) {
        return { kind: 'auth', detail: 'LinkedIn /voyager/api/me HTTP ' + res.status };
      }
      if (!res.ok) return { kind: 'http', httpStatus: res.status };
      const d = await res.json();
      const mini = d && d.miniProfile;
      if (!mini || !mini.publicIdentifier) {
        return { kind: 'auth', detail: 'LinkedIn /voyager/api/me 200 but miniProfile missing' };
      }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Run the site's login flow (e.g. `cli auth linkedin`) and complete LinkedIn login to mint an li_at cookie.
  2. Point the automation at the browser profile where you are already logged into LinkedIn.
  3. Persist the profile directory between runs so the cookie survives restarts.
  4. Re-login whenever LinkedIn expires the session; treat this error as the trigger for re-auth.

Example fix

// before
await run('linkedin company nvidia'); // AuthRequiredError: LinkedIn li_at cookie missing
// after
await run('auth linkedin');           // interactive login, stores li_at
await run('linkedin company nvidia');
Defensive patterns

Strategy: validation

Validate before calling

const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });
if (!cookies.some(c => c.name === 'li_at' && c.value)) {
  await run('auth linkedin'); // login before scraping
}

Type guard

function hasLinkedInSession(cookies) {
  return Array.isArray(cookies) && cookies.some(c => c.name === 'li_at' && !!c.value);
}

Try / catch

try {
  await run('linkedin company nvidia');
} catch (e) {
  if (e instanceof AuthRequiredError || /li_at cookie missing/.test(e.message)) {
    await run('auth linkedin');
    return run('linkedin company nvidia');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any linkedin command without a prior successful login: the browser profile has no cookies, cookies expired/were cleared, or getCookies({url:'https://www.linkedin.com'}) returns no li_at entry.

Common situations: Fresh automation environment with no logged-in profile; LinkedIn invalidated sessions (users are logged out periodically); clearing browser data between runs; wrong profile directory configured for the headless browser.

Related errors


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