{"record":{"id":"ea489d50dc51c003","repo":"jackwener/OpenCLI","slug":"invalid-linkedin-learning-course-url-s","errorCode":null,"errorMessage":"Invalid LinkedIn Learning course URL: \"${s}\"","messagePattern":"Invalid LinkedIn Learning course URL: \"(.+?)\"","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/linkedin-learning/course.js","lineNumber":24,"sourceCode":"import { DOMAIN, fetchLinkedInLearningApi, normalizeWhitespace } from './shared.js';\n\nfunction parseSlug(value) {\n    const s = normalizeWhitespace(value);\n    if (!s) throw new ArgumentError('<slug> is required');\n    let slug = s;\n    if (/^https?:\\/\\//i.test(s)) {\n        let parsed;\n        try {\n            parsed = new URL(s);\n        } catch {\n            throw new ArgumentError(`Invalid LinkedIn Learning URL: \"${s}\"`);\n        }\n        const host = parsed.hostname.toLowerCase();\n        if (host !== 'linkedin.com' && host !== 'www.linkedin.com') {\n            throw new ArgumentError(`Invalid LinkedIn Learning host: \"${parsed.hostname}\"`);\n        }\n        const m = parsed.pathname.match(/^\\/learning\\/([^/?#]+)/);\n        if (!m) throw new ArgumentError(`Invalid LinkedIn Learning course URL: \"${s}\"`);\n        slug = m[1];\n    } else {\n        const m = s.match(/^\\/?learning\\/([^/?#]+)/);\n        slug = m ? m[1] : s;\n    }\n    if (!/^[a-zA-Z0-9-_]+$/.test(slug)) {\n        throw new ArgumentError(`Invalid LinkedIn Learning slug: \"${slug}\"`);\n    }\n    return slug;\n}\n\nfunction parseCourse(el, slug) {\n    const title = normalizeWhitespace(el?.title);\n    if (!title) return null;\n    const description = typeof el?.description === 'string'\n        ? el.description\n        : (el?.description?.text || '');\n    const duration = el?.duration?.unit === 'SECOND' ? String(el.duration.duration ?? '') : '';","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin-learning/course.js#L6-L42","documentation":"Thrown by parseSlug when the input is a valid http(s) URL on linkedin.com/www.linkedin.com but its pathname does not start with /learning/<segment>. The library uses that path segment as the course slug, so a LinkedIn URL without a /learning/ path cannot be resolved to a course.","triggerScenarios":"Calling the linkedin-learning course command with an on-domain URL such as 'https://www.linkedin.com/posts/some-post' or 'https://www.linkedin.com/learningPaths/x' (regex only matches /learning/<segment>), or a bare 'https://www.linkedin.com/'.","commonSituations":"Pasting a LinkedIn post, profile, or learning-path URL instead of a course URL; copying the site homepage; a domain change in LinkedIn's URL scheme (e.g. /learn/ vs /learning/) in newer product areas.","solutions":["Pass the course slug directly instead of a URL.","Use a URL whose path is /learning/<slug>, e.g. https://www.linkedin.com/learning/agentic-ai-basics/.","Extract the slug from the URL you have and pass '/learning/<slug>' or just '<slug>'."],"exampleFix":"// before\nopencli linkedin-learning course 'https://www.linkedin.com/posts/ai-course-123'\n// after\nopencli linkedin-learning course 'https://www.linkedin.com/learning/agentic-ai-build-your-first-agentic-ai-system/'","handlingStrategy":"validation","validationCode":"function extractSlug(v) {\n  if (/^https?:\\/\\//i.test(v)) {\n    const u = new URL(v);\n    const m = u.pathname.match(/^\\/learning\\/([^/?#]+)/);\n    return m ? m[1] : null;\n  }\n  const m = v.match(/^\\/?learning\\/([^/?#]+)/);\n  return m ? m[1] : v;\n}\n// if (extractSlug(arg) === null) convert URL before calling;","typeGuard":"function isLearningPathUrl(v) {\n  if (!/^https?:\\/\\//i.test(v)) return false;\n  try { return /^\\/learning\\/[^/?#]+/.test(new URL(v).pathname); }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  run(['linkedin-learning', 'course', arg]);\n} catch (e) {\n  if (String(e.message).includes('Invalid LinkedIn Learning course URL')) {\n    // fall back to slug-only input or a /learning/<slug> URL\n  } else throw e;\n}","preventionTips":["Only pass URLs copied from /learning/ pages; post/profile/learning-path URLs are unsupported.","Extract the slug from the address bar and pass just the slug.","Document that the accepted URL shape is exactly https://www.linkedin.com/learning/<slug>/."],"tags":["argument-validation","url-parsing","linkedin-learning"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}