jackwener/OpenCLI · error · CommandExecutionError

LinkedIn Learning courses lookup returned malformed course d

Error message

LinkedIn Learning courses lookup returned malformed course detail: missing title

What it means

A course element was returned for the slug, but parseCourse returned null because normalizeWhitespace(el.title) was empty. Since the CLI's primary column is the title, the library treats a title-less course element as a malformed course detail rather than emitting an empty row.

Source

Thrown at clis/linkedin-learning/course.js:89

        if (!page) throw new CommandExecutionError('Browser session required for linkedin-learning course');
        const slug = parseSlug(args.slug);

        const url = `https://www.linkedin.com/learning-api/courses?q=slug&slug=${encodeURIComponent(slug)}`;
        const result = await fetchLinkedInLearningApi(page, url);
        if (!result?.json) {
            throw new CommandExecutionError(`LinkedIn Learning courses lookup failed: ${result?.error ?? 'no payload'}`);
        }
        const elements = result.json?.elements;
        if (!Array.isArray(elements)) {
            throw new CommandExecutionError('LinkedIn Learning courses lookup returned malformed payload: missing elements array');
        }
        const el = elements[0];
        if (!el) {
            throw new EmptyResultError(`No LinkedIn Learning course found for slug "${slug}"`);
        }
        const row = parseCourse(el, slug);
        if (!row) {
            throw new CommandExecutionError('LinkedIn Learning courses lookup returned malformed course detail: missing title');
        }
        return [row];
    },
});

export const __test__ = { parseSlug, parseCourse };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw element payload (result.json.elements[0]) to see where the title actually lives.
  2. Retry or pick a different slug — the entry may be a restricted/placeholder course.
  3. If LinkedIn changed the schema, update parseCourse to the new title location and update the library.
  4. Search for the course by title to confirm the correct, fully published slug.
Defensive patterns

Strategy: try-catch

Validate before calling

// No caller-side pre-check possible; requires inspecting the API element.
// Log the raw element to diagnose:
console.debug(JSON.stringify(rawCourseElement?.title));

Type guard

function hasTitle(el) {
  return el != null && typeof el.title === 'string' && el.title.trim().length > 0;
}

Try / catch

try {
  return await runCommand('linkedin-learning', 'course', [slug]);
} catch (e) {
  if (e.message.includes('malformed course detail')) {
    return runCommand('linkedin-learning', 'search', [slugToKeywords(slug)]); // get a better-shaped row
  }
  throw e;
}

Prevention

When it happens

Trigger: elements[0] exists but lacks a usable title field (missing, null, or whitespace-only el.title, or el.title.text empty), e.g. restricted/placeholder course entries or an API payload-shape change.

Common situations: LinkedIn returning partial/teaser course data for region-locked courses; API schema change moving the title to a nested localized object; a draft/unpublished course matched by stale search indexes.

Understand the failure class

Related errors


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