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

  1. Retry the command — reload /messaging/ so a fresh messengerConversations URL and token are captured.
  2. Check whether the response was an error envelope (JSON with message/status) rather than normalized data.
  3. Update the CLI; if LinkedIn renamed the envelope, a newer version may parse the new shape.
  4. 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

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

Related errors


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