jackwener/OpenCLI · error · CommandExecutionError

${label} returned malformed response

Error message

${label} returned malformed response

What it means

requireFetchResult expects a plain object response; if the fetch result is null, not an object, or an array, it throws CommandExecutionError('...returned malformed response'). This guards against empty bodies, HTML login pages, or unexpected non-JSON payloads from the Sales Navigator endpoints.

Source

Thrown at clis/linkedin/salesnav-message.js:168

    }
  })()`;
}

function requireFetchResult(result, label, { requireJson = true } = {}) {
  if (Array.isArray(result)) {
    const [kind, status, json, text, error] = result;
    result = {
      authRequired: kind === 'auth',
      error: kind === 'error' ? error || `HTTP ${status}` : '',
      status,
      json,
      text,
    };
  }
  if (result?.authRequired) throw new AuthRequiredError(LINKEDIN_DOMAIN, `${label} auth failed.`);
  if (result?.error) throw new CommandExecutionError(`${label} failed`, result.error);
  if (!result || typeof result !== 'object' || Array.isArray(result)) {
    throw new CommandExecutionError(`${label} returned malformed response`);
  }
  if (requireJson && (!result.json || typeof result.json !== 'object' || Array.isArray(result.json))) {
    throw new CommandExecutionError(`${label} returned malformed response`, 'missing_json');
  }
  return result;
}

function salesPageShowsSentMessage(text, recipientName) {
  const normalizedText = normalizeWhitespace(text);
  const firstName = normalizeWhitespace(recipientName).split(' ')[0];
  return normalizedText.includes('You sent a Sales Navigator message')
    && (!firstName || normalizedText.includes(firstName));
}

async function getCsrf(page) {
  const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });
  const jsession = cookies.find((c) => c.name === 'JSESSIONID')?.value;
  if (!jsession) throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn JSESSIONID cookie not found. Please sign in to LinkedIn.');

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Print/capture result.text to see what LinkedIn actually returned
  2. Re-authenticate if the body is a login/HTML page
  3. Add a delay after page.goto(SALES_HOME) before API calls so the session settles
  4. Update parsing if LinkedIn changed the response envelope

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetchProfile(url);
if (res.status >= 400 || /<html/i.test(res.text || '')) throw new Error('non-JSON response: ' + res.status);

Type guard

const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);

Try / catch

try {
  const r = await fetchProfileData(page, urn);
} catch (err) {
  if (/malformed response/.test(err.message)) {
    await page.wait(3); await reauthenticateIfLoginScreen(page); return retry();
  }
  throw err;
}

Prevention

When it happens

Trigger: A fetch returns an empty/HTML body (redirect to login or challenge page), or the unwrapped response is an array instead of an object — checked in profileResult, creditsResult, sendResult, creditsAfterResult.

Common situations: LinkedIn serving an anti-bot interstitial instead of API JSON; network proxy returning HTML error pages; hitting the API before the Sales Nav session is fully established; LinkedIn schema change altering the envelope.

Understand the failure class

Related errors


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