{"record":{"id":"7aba83d99c750d97","repo":"jackwener/OpenCLI","slug":"linkedin-thread-history-did-not-stabilize-before","errorCode":null,"errorMessage":"LinkedIn thread history did not stabilize before --max-scrolls; refusing to return a partial snapshot.","messagePattern":"LinkedIn thread history did not stabilize before --max-scrolls; refusing to return a partial snapshot\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/linkedin/thread-snapshot.js","lineNumber":347,"sourceCode":"          scrollStable: firstDiscovery.scrollStable,\n        };\n      } else {\n        discovery = retried;\n      }\n    }\n    if (discovery?.authRequired) {\n      throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn thread-snapshot requires an active signed-in LinkedIn browser session.');\n    }\n    if (!discovery || typeof discovery !== 'object' || Array.isArray(discovery) || !Array.isArray(discovery.apiUrls)) {\n      throw new CommandExecutionError('LinkedIn thread-snapshot returned a malformed API discovery payload.');\n    }\n\n    const actualUrl = canonicalizeLinkedInThreadUrl(discovery.url || '');\n    if (threadUrl && actualUrl && threadUrl !== actualUrl) {\n      throw new CommandExecutionError('LinkedIn thread-snapshot blocked: thread_url_mismatch', `Expected ${threadUrl}; actual ${actualUrl}`);\n    }\n    if (maxScrolls >= 4 && discovery.scrollAttempts >= maxScrolls && discovery.scrollStable === false) {\n      throw new CommandExecutionError('LinkedIn thread history did not stabilize before --max-scrolls; refusing to return a partial snapshot.');\n    }\n\n    const apiUrls = validateThreadApiUrls(discovery.apiUrls, threadUrl);\n    const csrf = await requireLinkedInCookie(page, 'LinkedIn thread-snapshot');\n    const fetched = unwrapEvaluateResult(\n      await page.evaluate(buildFetchThreadPagesScript(apiUrls, csrf)),\n    );\n    if (fetched?.authRequired) {\n      throw new AuthRequiredError(LINKEDIN_DOMAIN, `LinkedIn messengerMessages API authentication failed: ${fetched.error}`);\n    }\n    if (!fetched || fetched.error || !Array.isArray(fetched.pages)) {\n      throw new CommandExecutionError(`LinkedIn messengerMessages API returned an unexpected response: ${fetched?.error || 'no data'}`);\n    }\n\n    const parsed = parseThreadPages(fetched.pages);\n    const recipient = parsed.recipientNames.join(', ');\n    const latestMessageText = parsed.messages[parsed.messages.length - 1]?.text || '';\n    const normalized = {","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/linkedin/thread-snapshot.js#L329-L365","documentation":"thread-snapshot scrolls the thread upward to load older messages and requires scroll history to stabilize (discovery.scrollStable === true) before returning. If it consumed maxScrolls (default 30, and >= 4) attempts without the message history settling, it refuses to return a partial snapshot and throws this CommandExecutionError.","triggerScenarios":"Running thread-snapshot on a very long thread where LinkedIn loads older pages lazily and scrollAttempts reaches --max-scrolls with scrollStable still false — e.g. slow network, heavy thread, or aggressive throttling of the messengerMessages pagination API.","commonSituations":"Snapshotting multi-year conversations with thousands of messages; slow/limited connectivity causing lazy-load requests to time out; setting --max-scrolls too low (e.g. 5) on a long thread; LinkedIn rate-limiting the pagination endpoint.","solutions":["Increase --max-scrolls (e.g. 60 or 100) to give the thread more room to load its full history","Improve network conditions or retry later if LinkedIn is throttling the pagination API","Accept a truncated view only for short threads where stabilization isn't needed","Re-run the command; scroll state is not cached so a retry starts fresh"],"exampleFix":"// before\nlinkedin thread-snapshot --thread-url <url> --max-scrolls 10\n// after\nlinkedin thread-snapshot --thread-url <url> --max-scrolls 100","handlingStrategy":"retry","validationCode":"// Estimate thread length first; long threads need a high max-scrolls\nconst maxScrolls = estimatedMessageCount > 200 ? 100 : 30;","typeGuard":null,"tryCatchPattern":"try {\n  await threadSnapshot({ threadUrl, maxScrolls });\n} catch (e) {\n  if (String(e.message).includes('did not stabilize')) {\n    await threadSnapshot({ threadUrl, maxScrolls: maxScrolls * 2 }); // retry with more headroom\n  } else throw e;\n}","preventionTips":["Set --max-scrolls generously for long-lived threads","Run on a fast, stable network connection","Avoid concurrent LinkedIn commands that could trigger pagination throttling","Retry before accepting failure — scroll state is not cached"],"tags":["linkedin","incomplete-data","browser-automation","pagination"],"backgroundTag":"incomplete-pagination-timeout","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}