jackwener/OpenCLI · error · CommandExecutionError
LinkedIn messaging API returned malformed normalized payload
Error message
LinkedIn messaging API returned malformed normalized payload: missing included array
What it means
parseConversations() in inbox.js parses LinkedIn's normalized messenger JSON (accept: application/vnd.linkedin.normalized+json+2.1) and requires normalized.included to be an array — the flat entity list from which conversations, participants and messages are resolved by URN. Any non-object payload or missing/included non-array triggers this CommandExecutionError.
Source
Thrown at clis/linkedin/inbox.js:65
accept: 'application/vnd.linkedin.normalized+json+2.1',
'x-restli-protocol-version': '2.0.0',
},
});
if (res.status === 401 || res.status === 403) return { authRequired: true, error: 'HTTP ' + res.status };
if (!res.ok) return { error: 'HTTP ' + res.status };
return { json: await res.json() };
} catch (e) {
return { error: 'fetch failed: ' + ((e && e.message) || String(e)) };
}
}
// Parse LinkedIn's normalized messaging JSON into plain conversation rows.
// `included` is a flat entity array; conversations reference participants and
// messages by URN, which we resolve through a urn->entity index. Exported for
// unit testing against a captured fixture.
function parseConversations(normalized, mailboxUrn) {
if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized) || !Array.isArray(normalized.included)) {
throw new CommandExecutionError('LinkedIn messaging API returned malformed normalized payload: missing included array');
}
const included = normalized.included;
const byUrn = new Map();
for (const o of included) {
if (o && o.entityUrn) byUrn.set(o.entityUrn, o);
}
const norm = (s) => String(s == null ? '' : s).replace(/\s+/g, ' ').trim();
const participantInfo = (p) => {
if (!p) return { name: '', kind: '' };
const pt = p.participantType || {};
if (pt.organization && pt.organization.name) return { name: norm(pt.organization.name.text), kind: 'organization' };
if (pt.member) {
const fn = pt.member.firstName && pt.member.firstName.text;
const ln = pt.member.lastName && pt.member.lastName.text;
return { name: norm([fn, ln].filter(Boolean).join(' ')), kind: 'member' };
}
if (pt.agent && pt.agent.name) return { name: norm(pt.agent.name.text), kind: 'agent' };View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the command — reload /messaging/ so a fresh messengerConversations URL and token are captured.
- Check whether the response was an error envelope (JSON with message/status) rather than normalized data.
- Update the CLI; if LinkedIn renamed the envelope, a newer version may parse the new shape.
- In tests, regenerate the captured fixture from a live session instead of using an outdated one.
Example fix
// before (fixture outdated)
parseConversations({}, 'urn:li:fsd_mailbox:me');
// Error: ...missing included array
// after
parseConversations({ included: [/* entities from a fresh capture */] }, 'urn:li:fsd_mailbox:me'); Defensive patterns
Strategy: type-guard
Validate before calling
function isNormalizedPayload(p) {
return p && typeof p === 'object' && !Array.isArray(p) && Array.isArray(p.included);
}
// before calling parseConversations on a captured/raw payload:
if (!isNormalizedPayload(raw)) throw new Error('Payload is not normalized messaging JSON.'); Type guard
function isNormalizedMessagingJson(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value) && Array.isArray(value.included);
} Try / catch
try {
const convs = await opencli.linkedin.inbox({ limit: 20 });
} catch (e) {
if (/missing included array/.test(e.message || '')) {
console.warn('Messaging payload was not normalized JSON — reload /messaging/ and retry.');
return [];
}
throw e;
} Prevention
- Always reload /messaging/ so the captured request URL and token are current.
- Regenerate test fixtures from live sessions when LinkedIn ships changes.
- Check for JSON error envelopes (message/status keys) when this fires with HTTP 200.
- Keep the Accept header as application/vnd.linkedin.normalized+json+2.1.
When it happens
Trigger: Re-issuing the messengerConversations GraphQL request when the response is an error JSON object, an empty body parsed as something unexpected, or LinkedIn changes the normalized envelope (e.g. renamed included), including when a captured fixture/unit test passes a malformed payload.
Common situations: LinkedIn schema change altering the top-level shape; the request URL lifted from the Performance API returning an error envelope with HTTP 200; acceptance header mismatch causing plain JSON instead of normalized JSON; stale fixtures in tests.
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
- LinkedIn messaging API returned a conversation without threa
- LinkedIn connection miniProfile field ${field} was malformed
- LinkedIn connections returned an element without a miniProfi
- LinkedIn connections API returned a malformed payload: missi
- LinkedIn jobs preferences returned malformed preferences pay
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/f9b70a2e304d5aa1.
Report an issue: GitHub.