jackwener/OpenCLI · error · CommandExecutionError
LinkedIn thread-snapshot returned a malformed API discovery
Error message
LinkedIn thread-snapshot returned a malformed API discovery payload.
What it means
The discovery step returns the intercepted thread info including the captured apiUrls. The command throws this CommandExecutionError when discovery is null/not an object/is an array, or lacks a valid apiUrls array — i.e. the scraper could not identify any messengerMessages network requests for the open thread.
Source
Thrown at clis/linkedin/thread-snapshot.js:339
if (discovery && Array.isArray(discovery.apiUrls) && discovery.apiUrls.length === 0) {
const firstDiscovery = discovery;
await page.wait(4);
const retried = unwrapEvaluateResult(await page.evaluate(buildThreadApiDiscoveryScript(0)));
if (retried && typeof retried === 'object' && !Array.isArray(retried)) {
discovery = {
...retried,
scrollAttempts: firstDiscovery.scrollAttempts,
scrollStable: firstDiscovery.scrollStable,
};
} else {
discovery = retried;
}
}
if (discovery?.authRequired) {
throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn thread-snapshot requires an active signed-in LinkedIn browser session.');
}
if (!discovery || typeof discovery !== 'object' || Array.isArray(discovery) || !Array.isArray(discovery.apiUrls)) {
throw new CommandExecutionError('LinkedIn thread-snapshot returned a malformed API discovery payload.');
}
const actualUrl = canonicalizeLinkedInThreadUrl(discovery.url || '');
if (threadUrl && actualUrl && threadUrl !== actualUrl) {
throw new CommandExecutionError('LinkedIn thread-snapshot blocked: thread_url_mismatch', `Expected ${threadUrl}; actual ${actualUrl}`);
}
if (maxScrolls >= 4 && discovery.scrollAttempts >= maxScrolls && discovery.scrollStable === false) {
throw new CommandExecutionError('LinkedIn thread history did not stabilize before --max-scrolls; refusing to return a partial snapshot.');
}
const apiUrls = validateThreadApiUrls(discovery.apiUrls, threadUrl);
const csrf = await requireLinkedInCookie(page, 'LinkedIn thread-snapshot');
const fetched = unwrapEvaluateResult(
await page.evaluate(buildFetchThreadPagesScript(apiUrls, csrf)),
);
if (fetched?.authRequired) {
throw new AuthRequiredError(LINKEDIN_DOMAIN, `LinkedIn messengerMessages API authentication failed: ${fetched.error}`);
}View on GitHub (pinned to 49907e53dc)
Solutions
- Verify --thread-url is a canonical LinkedIn messaging thread URL (linkedin.com/messaging/thread/...)
- Increase wait time / max-scrolls so the API requests actually fire and are captured before parsing
- Disable proxies/extensions that might block or rewrite voyager API requests
- Update the library if LinkedIn renamed the GraphQL endpoint or queryId pattern
Example fix
// before: thread URL that never triggers messengerMessages
await run('linkedin thread-snapshot', { 'thread-url': 'https://www.linkedin.com/messaging/' });
// after: exact thread URL
await run('linkedin thread-snapshot', { 'thread-url': 'https://www.linkedin.com/messaging/thread/2-Yzg1Zj...' }); Defensive patterns
Strategy: validation
Validate before calling
// Confirm the URL is a messaging thread URL that triggers the messengerMessages API
const u = new URL(threadUrl);
if (u.hostname !== 'www.linkedin.com' || !u.pathname.startsWith('/messaging/thread/')) {
throw new Error('Provide a canonical LinkedIn messaging thread URL');
} Type guard
const isMessagingThreadUrl = (v) => {
try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'www.linkedin.com' && u.pathname.startsWith('/messaging/thread/'); }
catch { return false; }
}; Try / catch
try {
const snap = await run('linkedin thread-snapshot', { 'thread-url': url });
} catch (err) {
if (String(err.message).includes('malformed API discovery payload')) {
// no messengerMessages requests captured: increase waits/scrolls and retry
} else throw err;
} Prevention
- Pass exact /messaging/thread/... URLs, not the messaging inbox
- Give the page enough wait time and scrolls for the API calls to fire
- Disable proxies/extensions that block or rewrite voyager API traffic
When it happens
Trigger: The page loaded but no matching GraphQL request was captured (network interception failed, wrong URL passed, messaging page failed to render); a retry still returned a malformed shape; discovery returned an unexpected object after LinkedIn's frontend changed its request pattern.
Common situations: Slow page load — scrolls finished before the API fired; browser extension or proxy blocking voyager API calls; LinkedIn UI changes so no request matches the messengerMessages pattern; passing a non-messaging URL that never triggers the API.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Ctrip flight requires browser response interception
- linkedin.com: LinkedIn li_at cookie missing
- linkedin.com: ${result.detail}
- HTTP ${result.httpStatus} from /voyager/api/me
- LinkedIn Learning whoami failed: ${result.detail}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/21c71984ebce0de5.
Report an issue: GitHub.