jackwener/OpenCLI · error · CommandExecutionError
Sales Navigator messaging thread API returned malformed payl
Error message
Sales Navigator messaging thread API returned malformed payload
What it means
Thrown by parseSalesnavThreadMessages (clis/linkedin/salesnav-thread.js:87) when the thread payload passed from the Sales Navigator messaging API is null or not a plain object. The parser expects the decoded REST decoration object containing id, messages, and participant data. This is a defensive schema check indicating the API returned something structurally different from what the pinned decoration (THREAD_DECORATION) produces.
Source
Thrown at clis/linkedin/salesnav-thread.js:87
function participantName(profile) {
return normalizeWhitespace(profile?.fullName || [profile?.firstName, profile?.lastName].filter(Boolean).join(' '));
}
function participantIndex(thread) {
const resolution = thread?.participantsResolutionResults || {};
const participants = Array.isArray(thread?.participants) ? thread.participants : Object.keys(resolution);
const byUrn = new Map();
for (const urn of participants) {
const profile = resolution[urn] || { entityUrn: urn };
byUrn.set(urn, profile);
}
return byUrn;
}
function parseSalesnavThreadMessages(thread) {
if (!thread || typeof thread !== 'object') {
throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed payload');
}
const threadId = normalizeWhitespace(thread?.id || '');
if (!threadId) {
throw new CommandExecutionError('Sales Navigator messaging thread API returned a thread without id');
}
if (!Array.isArray(thread?.messages)) {
throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed messages');
}
const byUrn = participantIndex(thread);
const messages = thread.messages;
const rows = messages.map((message) => {
if (!message || typeof message !== 'object') {
throw new CommandExecutionError('Sales Navigator messaging thread API returned malformed message row');
}
const deliveredAt = Number(message?.deliveredAt || 0);
const senderProfile = byUrn.get(message?.author);
return {
message_id: normalizeWhitespace(message?.id || ''),View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run the command; transient API errors or aborted fetches are the most frequent cause.
- Confirm the session is valid (Sales Navigator inbox loads in the automation browser); re-authenticate if redirected to login.
- Check whether the pinned THREAD_DECORATION version still matches a live /sales-api thread response; refresh it from the network tab if LinkedIn redeployed.
- Log the raw fetchSalesnavJson output before parsing to capture what LinkedIn actually returned, then report/update the decoration constant.
Example fix
// before
const thread = await fetchSalesnavJson(page, csrf, url, 'Sales Navigator messaging thread API');
const messages = parseSalesnavThreadMessages(thread);
// after (guard before parse)
const thread = await fetchSalesnavJson(page, csrf, url, 'Sales Navigator messaging thread API');
if (!thread || typeof thread !== 'object') {
throw new Error('thread API returned non-object; check session/decoration');
}
const messages = parseSalesnavThreadMessages(thread); Defensive patterns
Strategy: type-guard
Validate before calling
// Guard the thread payload before parsing:
if (!thread || typeof thread !== 'object' || Array.isArray(thread)) {
throw new Error('thread API returned a non-object payload; check session and decoration version');
} Type guard
function isThreadObject(t) {
return typeof t === 'object' && t !== null && !Array.isArray(t);
} Try / catch
try {
const messages = parseSalesnavThreadMessages(thread);
} catch (err) {
if (err instanceof CommandExecutionError && /malformed payload/.test(err.message)) {
// re-authenticate and refresh decoration before retrying
await ensureSalesnavSession(page);
return fetchAndParseThread();
}
throw err;
} Prevention
- Verify the session is live (inbox loads) before fetching thread payloads.
- After LinkedIn redeploys, re-capture THREAD_DECORATION from the network tab and update the constant.
- Log raw fetchSalesnavJson output when parsing fails so the actual payload is preserved for diagnosis.
When it happens
Trigger: fetchSalesnavJson returned null/undefined because the page.evaluate wrapper was unwrapped to nothing; LinkedIn returned an error body or empty object instead of a thread; the decorationId version changed so the response no longer matches the expected shape and downstream unwrapping yields a non-object.
Common situations: LinkedIn Sales Navigator redeploy changing response decoration (the file comments warn decoration versions get bumped); expired/limited session causing an HTML or error JSON body to be parsed as a thread; passing the result of a failed fetch (null) straight into parsing.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Sales Navigator messaging thread API returned malformed mess
- Sales Navigator messaging thread API returned a thread witho
- Sales Navigator messaging thread API returned malformed mess
- Sales Navigator lead search API returned malformed payload
- Sales Navigator lead search API returned malformed lead row
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/77a50af93d3739a8.
Report an issue: GitHub.