jackwener/OpenCLI · error · ArgumentError

Invalid LinkedIn Learning URL: "${s}"

Error message

Invalid LinkedIn Learning URL: "${s}"

What it means

If the slug argument looks like an HTTP(S) URL, parseSlug parses it with new URL; when the URL constructor throws (malformed URL), it throws ArgumentError with the original string embedded. It guards against syntactically invalid URLs being passed instead of plain slugs.

Source

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

/**
 * LinkedIn Learning course detail by slug, via /learning-api/courses?q=slug.
 */
import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
import { DOMAIN, fetchLinkedInLearningApi, normalizeWhitespace } from './shared.js';

function parseSlug(value) {
    const s = normalizeWhitespace(value);
    if (!s) throw new ArgumentError('<slug> is required');
    let slug = s;
    if (/^https?:\/\//i.test(s)) {
        let parsed;
        try {
            parsed = new URL(s);
        } catch {
            throw new ArgumentError(`Invalid LinkedIn Learning URL: "${s}"`);
        }
        const host = parsed.hostname.toLowerCase();
        if (host !== 'linkedin.com' && host !== 'www.linkedin.com') {
            throw new ArgumentError(`Invalid LinkedIn Learning host: "${parsed.hostname}"`);
        }
        const m = parsed.pathname.match(/^\/learning\/([^/?#]+)/);
        if (!m) throw new ArgumentError(`Invalid LinkedIn Learning course URL: "${s}"`);
        slug = m[1];
    } else {
        const m = s.match(/^\/?learning\/([^/?#]+)/);
        slug = m ? m[1] : s;
    }
    if (!/^[a-zA-Z0-9-_]+$/.test(slug)) {
        throw new ArgumentError(`Invalid LinkedIn Learning slug: "${slug}"`);
    }
    return slug;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass just the course slug (the path segment after /learning/) instead of a malformed URL.
  2. Fix the URL: ensure it's complete and properly encoded, e.g. https://www.linkedin.com/learning/<slug>.
  3. Quote the URL in the shell so spaces/special characters don't break it.
  4. Decode/re-encode any odd percent sequences (%zz is invalid) before passing.

Example fix

// before
run(['linkedin-learning', 'course', 'https://www.linkedin.com/learning/ my course']); // ArgumentError
// after
run(['linkedin-learning', 'course', 'https://www.linkedin.com/learning/my-course']);
// or just the slug:
run(['linkedin-learning', 'course', 'my-course']);
Defensive patterns

Strategy: validation

Validate before calling

function coerceSlug(input) {
  const s = (input ?? '').trim();
  if (!s) throw new Error('slug required');
  if (/^https?:\/\//i.test(s)) {
    try { new URL(s); } catch { throw new Error(`not a valid URL: ${s}`); }
    const m = new URL(s).pathname.match(/^\/learning\/([^/?#]+)/);
    if (m) return m[1];
  }
  return s;
}

Type guard

function isWellFormedUrl(s) { try { new URL(s); return true; } catch { return false; } }

Try / catch

try {
  await runCommand(['linkedin-learning', 'course', input]);
} catch (e) {
  if (e.name === 'ArgumentError' && e.message.includes('Invalid LinkedIn Learning URL')) {
    const slug = extractSlugManually(input); // fallback: strip prefix/suffix
    return runCommand(['linkedin-learning', 'course', slug]);
  }
  throw e;
}

Prevention

When it happens

Trigger: Value starts with http:// or https:// but new URL(s) rejects it — e.g. 'https://', 'https://www.linkedin.com/learning/%zz' (bad percent-encoding), or a truncated paste like 'https://www.linkedin.com/learning/ course'.

Common situations: Truncated or whitespace-corrupted copy-paste of a course URL; shell word-splitting inserted a space into the URL; percent-encoding mangled by double-unescaping; missing scheme characters.

Related errors


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