{"record":{"id":"ab549f0f290f6c7f","repo":"jackwener/OpenCLI","slug":"linkedin-learning-searchv2-returned-malformed-payl","errorCode":null,"errorMessage":"LinkedIn Learning searchV2 returned malformed payload: missing elements array","messagePattern":"LinkedIn Learning searchV2 returned malformed payload: missing elements array","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin-learning/search.js","lineNumber":76,"sourceCode":"    args: [\n        { name: 'keywords', type: 'string', required: true, positional: true, help: 'Search keywords, e.g. \"AI agent\"' },\n        { name: 'limit', type: 'int', default: 10, help: `Maximum results to return (1-${MAX_LIMIT})` },\n    ],\n    columns: ['rank', 'type', 'title', 'instructor', 'difficulty', 'duration_sec', 'rating', 'rating_count', 'viewers', 'url'],\n    func: async (page, args) => {\n        if (!page) throw new CommandExecutionError('Browser session required for linkedin-learning search');\n        const keywords = normalizeWhitespace(args.keywords);\n        if (!keywords) throw new ArgumentError('--keywords is required');\n        const limit = parseLimit(args.limit);\n\n        const url = `https://www.linkedin.com/learning-api/searchV2?keywords=${encodeURIComponent(keywords)}&q=keywords`;\n        const result = await fetchLinkedInLearningApi(page, url);\n        if (!result?.json) {\n            throw new CommandExecutionError(`LinkedIn Learning searchV2 failed: ${result?.error ?? 'no payload'}`);\n        }\n        const elements = result.json?.elements;\n        if (!Array.isArray(elements)) {\n            throw new CommandExecutionError('LinkedIn Learning searchV2 returned malformed payload: missing elements array');\n        }\n        if (elements.length === 0) {\n            throw new EmptyResultError(`No LinkedIn Learning results for \"${keywords}\"`);\n        }\n        const rows = [];\n        for (const el of elements) {\n            if (rows.length >= limit) break;\n            const row = parseRow(el, rows.length + 1);\n            if (row) rows.push(row);\n        }\n        if (rows.length === 0) {\n            throw new CommandExecutionError('LinkedIn Learning searchV2 returned no parseable rows with slug identity');\n        }\n        return rows;\n    },\n});\n\nexport const __test__ = {","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin-learning/search.js#L58-L94","documentation":"Thrown when the searchV2 API responds with valid JSON but that JSON lacks an 'elements' array at the top level. The CLI expects a RestLi-style collection payload; anything else (an error envelope, an HTML/JSON hybrid, or a changed schema) is treated as malformed. This guards the row-parsing loop from crashing on unexpected shapes.","triggerScenarios":"result.json exists but result.json.elements is not an Array — e.g. the endpoint returned {'status':404,'message':...} style RestLi error JSON, an included/wrapper object, or LinkedIn changed the searchV2 response schema.","commonSituations":"LinkedIn deprecating or reshaping the internal searchV2 API (schema drift), the endpoint returning an error envelope with HTTP 200, or an A/B-tested variant payload without elements.","solutions":["Log result.json keys to inspect the actual payload shape and confirm schema drift.","Update the CLI to the latest version that matches the current LinkedIn searchV2 schema.","Add a fallback to parse alternate payload shapes (e.g. result.json.data?.elements or included arrays).","Check LinkedIn Learning in the browser to see if search still works (indicates an API-side change, not local config)."],"exampleFix":"// before\nconst elements = result.json?.elements;\nif (!Array.isArray(elements)) throw new CommandExecutionError('...missing elements array');\n// after\nconst raw = result.json?.elements ?? result.json?.data?.elements;\nif (!Array.isArray(raw)) throw new CommandExecutionError('...missing elements array: keys=' + Object.keys(result.json ?? {}).join(','));","handlingStrategy":"type-guard","validationCode":"// after fetch, validate expected RestLi shape before consuming\nif (!result?.json || !Array.isArray(result.json.elements)) {\n  throw new Error('Unexpected searchV2 payload: ' + JSON.stringify(result?.json)?.slice(0, 200));\n}","typeGuard":"function isSearchV2Payload(json) {\n  return !!json && typeof json === 'object' && Array.isArray(json.elements) &&\n    json.elements.every(el => el && typeof el === 'object');\n}","tryCatchPattern":"try {\n  const rows = await search(page, { keywords });\n} catch (e) {\n  if (e.message.includes('malformed payload')) {\n    console.error('LinkedIn schema drift detected; update the CLI or inspect payload', e.message);\n  } else throw e;\n}","preventionTips":["Keep the CLI updated — internal LinkedIn APIs change without notice","Log payload keys when the guard fires to detect schema drift early","Pin/monitor CLI versions in CI so failures correlate with upgrades","Fall back to alternate payload shapes (data.elements / included) defensively"],"tags":["schema","api","linkedin","payload"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}