{"record":{"id":"7128f03bd74fae0f","repo":"jackwener/OpenCLI","slug":"linkedin-com-result-detail","errorCode":null,"errorMessage":"linkedin.com: ${result.detail}","messagePattern":"linkedin\\.com: (.+?)","errorType":"error_code","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/linkedin-learning/auth.js","lineNumber":42,"sourceCode":"      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      }\n      const firstName = (mini.firstName && (mini.firstName.text || mini.firstName)) || '';\n      const lastName = (mini.lastName && (mini.lastName.text || mini.lastName)) || '';\n      return {\n        ok: true,\n        public_id: String(mini.publicIdentifier),\n        plain_id: String(d.plainId || ''),\n        name: String((firstName + ' ' + lastName).trim()),\n      };\n    } catch (e) {\n      return { kind: 'exception', detail: String(e && e.message || e) };\n    }\n  })()`);\n  if (result?.kind === 'auth') throw new AuthRequiredError('linkedin.com', result.detail);\n  if (result?.kind === 'http') throw new CommandExecutionError(`HTTP ${result.httpStatus} from /voyager/api/me`);\n  if (result?.kind === 'exception') throw new CommandExecutionError(`LinkedIn Learning whoami failed: ${result.detail}`);\n  if (!result?.ok) throw new CommandExecutionError(`Unexpected LinkedIn Learning probe: ${JSON.stringify(result)}`);\n  return { public_id: result.public_id, plain_id: result.plain_id, name: result.name };\n}\n\nregisterSiteAuthCommands({\n  site: 'linkedin-learning',\n  domain: 'linkedin.com',\n  loginUrl: 'https://www.linkedin.com/login?session_redirect=%2Flearning%2F',\n  columns: ['public_id', 'plain_id', 'name'],\n  quickCheck: hasLinkedinSessionCookie,\n  verify: verifyLinkedinLearningIdentity,\n  poll: async (page) => {\n    if (!await hasLinkedinSessionCookie(page)) {\n      throw new AuthRequiredError('linkedin.com', 'Waiting for LinkedIn li_at cookie');\n    }\n    return verifyLinkedinLearningIdentity(page);","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin-learning/auth.js#L24-L60","documentation":"The in-page whoami probe against LinkedIn's /voyager/api/me returns { kind: 'auth', detail } when LinkedIn signals the Learning session is not authorized (login redirect / CSRF/auth failure inside the page). The command converts that into AuthRequiredError with the page-reported detail. Unlike 2252, a li_at cookie exists but the Learning session is still not accepted.","triggerScenarios":"page.evaluate probe returns kind:'auth' — LinkedIn /voyager/api/me responds with a redirect-to-login or 401/403-style auth failure despite li_at being present.","commonSituations":"li_at cookie valid for linkedin.com but LinkedIn Learning entitlement/session not established; LinkedIn changing Voyager API auth requirements; partial login (main site only); corporate SSO requiring re-auth for Learning.","solutions":["Re-run the login flow and make sure you land on https://www.linkedin.com/learning/ logged-in.","Open /learning/ in the automation browser and confirm the Learning UI shows your account.","Clear cookies and log in fresh — a stale li_at can pass the cookie check but fail Voyager auth.","Check the result.detail message for LinkedIn's specific reason (redirect vs 401) and act accordingly."],"exampleFix":"// before\n// probe returns {kind:'auth', detail:'login redirect'} — command aborts\n// after\nawait page.goto('https://www.linkedin.com/learning/'); // re-establish Learning session\nawait runCommand('linkedin-learning login'); // then retry whoami","handlingStrategy":"try-catch","validationCode":"// Confirm the Learning UI is actually logged in before probing\nawait page.goto('https://www.linkedin.com/learning/');\nconst loggedIn = await page.evaluate(() => !!document.querySelector('[data-li-session], .nav__me, a[href*=\"/learning/checklist\"]'));\nif (!loggedIn) await runInteractiveLogin('linkedin-learning');","typeGuard":"function isAuthFailure(result) { return result?.kind === 'auth' && typeof result.detail === 'string'; }","tryCatchPattern":"try {\n  const identity = await verifyLinkedinLearningIdentity(page);\n} catch (e) {\n  if (e.name === 'AuthRequiredError') {\n    // li_at exists but Learning session not accepted: re-login on /learning/\n    await page.goto('https://www.linkedin.com/learning/');\n    await runInteractiveLogin('linkedin-learning');\n    return verifyLinkedinLearningIdentity(page);\n  }\n  throw e;\n}","preventionTips":["Always land on /learning/ after login, not just linkedin.com.","Clear stale cookies and log in fresh when auth errors persist.","Watch for LinkedIn Voyager auth changes and update the probe headers.","Treat li_at presence as necessary but not sufficient — verify the Learning session too."],"tags":["auth","linkedin","session"],"backgroundTag":"auth-required-redirect","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}