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
- Print/capture result.text to see what LinkedIn actually returned
- Re-authenticate if the body is a login/HTML page
- Add a delay after page.goto(SALES_HOME) before API calls so the session settles
- 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
- Check result.text when it fails — HTML means login/challenge page
- Throttle request rate to avoid anti-bot interstitials
- Refresh the session before batch runs
- Pin/update parsing code when LinkedIn changes its envelope
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Sales Navigator messaging threads API returned malformed pay
- Sales Navigator messaging threads API returned malformed thr
- ${label} returned an unexpected response
- Sales Navigator lead search API returned malformed payload
- Sales Navigator messaging thread row missing id
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/438fcdf051a72047.
Report an issue: GitHub.