jackwener/OpenCLI · error · CommandExecutionError

LinkedIn services-read requires a /services/page/<id>/ URL

Error message

LinkedIn services-read requires a /services/page/<id>/ URL

What it means

normalizeServicesUrl validates the --services-url argument for linkedin services-read. After the safety check, the pathname must match /services/page/<id>/. Other LinkedIn URLs are rejected with this ArgumentError because the command only extracts data from Services pages.

Source

Thrown at clis/linkedin/services-read.js:23

  assertSafeLinkedinUrl,
  normalizeWhitespace,
  unwrapEvaluateResult,
} from './shared.js';

function normalizeProfileUrl(value) {
  const url = assertSafeLinkedinUrl(value || 'https://www.linkedin.com/in/me/', 'profile-url', '/in/me/');
  const parsed = new URL(url);
  if (!/^\/in\/[^/?#]+\/?$/.test(parsed.pathname)) {
    throw new CommandExecutionError('LinkedIn services-read requires a /in/<handle>/ profile URL');
  }
  return parsed.toString();
}

function normalizeServicesUrl(value) {
  const url = assertSafeLinkedinUrl(value, 'services-url', '/services/page/');
  const parsed = new URL(url);
  if (!/^\/services\/page\/[^/?#]+\/?$/.test(parsed.pathname)) {
    throw new CommandExecutionError('LinkedIn services-read requires a /services/page/<id>/ URL');
  }
  return parsed.toString();
}

function buildFindServicesUrlScript() {
  return String.raw`(() => {
    const link = Array.from(document.querySelectorAll('a[href*="/services/page/"]'))
      .map((a) => a.href || '')
      .find(Boolean);
    return { services_url: link || '' };
  })()`;
}

function buildServicesPageScript() {
  return String.raw`(() => {
    const clean = (s) => String(s || '').replace(/[\u00a0\u202f]+/g, ' ').replace(/\s+/g, ' ').trim();
    const lines = (document.body?.innerText || '').split(/\n+/).map(clean).filter(Boolean);
    const unique = (items) => Array.from(new Set(items.filter(Boolean)));

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass the full Services page URL like https://www.linkedin.com/services/page/<id>/ — copy it from the profile's Services link.
  2. Omit --services-url and let the command discover it from --profile-url or the default /in/me/ profile.
  3. If the id is missing from your saved URL, re-discover it via the profile rather than hand-editing the path.

Example fix

// before
await runCommand('linkedin services-read', ['--services-url', 'https://www.linkedin.com/in/jane-doe/']);
// after
await runCommand('linkedin services-read', ['--services-url', 'https://www.linkedin.com/services/page/1234abc/']);
Defensive patterns

Strategy: validation

Validate before calling

function isServicesUrl(u) {
  try { return /^\/services\/page\/[^/?#]+\/?$/.test(new URL(u, 'https://www.linkedin.com').pathname); }
  catch { return false; }
}
if (servicesUrl && !isServicesUrl(servicesUrl)) throw new Error('services-url must be /services/page/<id>/');

Type guard

function isLinkedInServicesUrl(v) {
  try {
    const p = new URL(String(v), 'https://www.linkedin.com');
    return /^\/services\/page\/[^/?#]+\/?$/.test(p.pathname);
  } catch { return false; }
}

Prevention

When it happens

Trigger: Passing --services-url that is not a /services/page/<id>/ path, e.g. a profile URL, a /services/ root link, or a truncated /services/page/ with no id.

Common situations: Pasting the profile URL into --services-url by mistake; LinkedIn redesigns the Services page and links move off /services/page/; copying a relative /services/ link without the page id.

Related errors


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