{"record":{"id":"1e71f402c2573c9c","repo":"jackwener/OpenCLI","slug":"linkedin-com-linkedin-li-at-cookie-missing","errorCode":null,"errorMessage":"linkedin.com: LinkedIn li_at cookie missing","messagePattern":"linkedin\\.com: LinkedIn li_at cookie missing","errorType":"error_code","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/linkedin-learning/auth.js","lineNumber":11,"sourceCode":"import { AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';\nimport { registerSiteAuthCommands } from '../_shared/site-auth.js';\n\nasync function hasLinkedinSessionCookie(page) {\n  const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });\n  return cookies.some(c => c.name === 'li_at' && c.value);\n}\n\nasync function verifyLinkedinLearningIdentity(page) {\n  if (!await hasLinkedinSessionCookie(page)) {\n    throw new AuthRequiredError('linkedin.com', 'LinkedIn li_at cookie missing');\n  }\n  await page.goto('https://www.linkedin.com/learning/');\n  await page.wait(2);\n  const result = await page.evaluate(`(async () => {\n    try {\n      const jsessionRaw = (document.cookie.split('; ').find(c => c.startsWith('JSESSIONID=')) || '').split('=')[1] || '';\n      const csrf = jsessionRaw.replace(/^\"|\"$/g, '');\n      if (!csrf) return { kind: 'auth', detail: 'LinkedIn JSESSIONID missing — csrf token unavailable' };\n      const res = await fetch('/voyager/api/me', { credentials: 'include', headers: { 'csrf-token': csrf, 'Accept': 'application/json' } });\n      if (res.status === 401 || res.status === 403) {\n        return { kind: 'auth', detail: 'LinkedIn /voyager/api/me HTTP ' + res.status };\n      }\n      if (!res.ok) return { kind: 'http', httpStatus: res.status };\n      const d = await res.json();\n      const mini = d && d.miniProfile;\n      if (!mini || !mini.publicIdentifier) {\n        return { kind: 'auth', detail: 'LinkedIn /voyager/api/me 200 but miniProfile missing' };\n      }","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin-learning/auth.js#L1-L29","documentation":"verifyLinkedinLearningIdentity checks the Puppeteer page for a non-empty LinkedIn 'li_at' session cookie before probing the Learning API. Without it, LinkedIn Learning endpoints redirect to a login page, so the command throws AuthRequiredError early. This tells the user they must log in to LinkedIn in the automation browser first.","triggerScenarios":"Calling any linkedin-learning command (via registerSiteAuthCommands verify flow) when page.getCookies({ url: 'https://www.linkedin.com' }) contains no 'li_at' cookie with a non-empty value.","commonSituations":"First run of the tool before ever logging in; expired LinkedIn session (li_at rotated/cleared); running with a fresh browser profile or in CI where no cookies exist; user logged out of LinkedIn elsewhere invalidating the session.","solutions":["Run the site's login flow (the registered loginUrl https://www.linkedin.com/login?session_redirect=%2Flearning%2F) and complete LinkedIn login in the automation browser.","Confirm li_at exists after login: document.cookie or page.getCookies shows li_at with a value.","If the session expired, log in again to get a fresh li_at cookie.","Ensure the browser profile used persists cookies between runs."],"exampleFix":"// before\nawait runCommand('linkedin-learning course <slug>'); // fails: no session\n// after\nawait runCommand('linkedin-learning login'); // complete LinkedIn login in the browser\nconst identity = await runCommand('linkedin-learning whoami');","handlingStrategy":"validation","validationCode":"const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });\nconst hasLiAt = cookies.some(c => c.name === 'li_at' && c.value);\nif (!hasLiAt) {\n  await startLoginFlow('https://www.linkedin.com/login?session_redirect=%2Flearning%2F');\n}","typeGuard":"function hasLiAtCookie(cookies) {\n  return Array.isArray(cookies) && cookies.some(c => c?.name === 'li_at' && typeof c.value === 'string' && c.value.length > 0);\n}","tryCatchPattern":"try {\n  const identity = await verifyLinkedinLearningIdentity(page);\n} catch (e) {\n  if (e.name === 'AuthRequiredError' && e.message.includes('li_at')) {\n    await runInteractiveLogin('linkedin-learning');\n    return verifyLinkedinLearningIdentity(page);\n  }\n  throw e;\n}","preventionTips":["Run the login flow before any Learning command in a fresh profile.","Use a persistent browser profile so li_at survives across runs.","Re-login when LinkedIn rotates or invalidates the session.","Check cookie presence as a quickCheck before long operations."],"tags":["auth","cookie","linkedin"],"backgroundTag":"auth-cookie-missing","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}