jackwener/OpenCLI · error · CommandExecutionError
Twitter device-follow returned non-JSON response: ${data.det
Error message
Twitter device-follow returned non-JSON response: ${data.detail || 'unknown parse error'} What it means
The in-page fetch of the device-follow GraphQL endpoint wraps response parsing; when the body cannot be parsed as JSON it reports errorKind 'non_json' with a detail string, which the Node side rethrows as a CommandExecutionError. This indicates the endpoint answered, but not with the expected JSON payload.
Source
Thrown at clis/twitter/device-follow.js:160
'X-Csrf-Token': ct0,
'X-Twitter-Auth-Type': 'OAuth2Session',
'X-Twitter-Active-User': 'yes',
});
const data = await page.evaluate(`async () => {
try {
const r = await fetch("${apiUrl}", { method: "GET", headers: ${headers}, credentials: 'include' });
if (!r.ok) return { error: r.status };
try {
return await r.json();
} catch (e) {
return { errorKind: 'non_json', detail: String(e && e.message || e) };
}
} catch (e) {
return { errorKind: 'exception', detail: String(e && e.message || e) };
}
}`);
if (data?.errorKind === 'non_json') {
throw new CommandExecutionError(`Twitter device-follow returned non-JSON response: ${data.detail || 'unknown parse error'}`);
}
if (data?.errorKind === 'exception') {
throw new CommandExecutionError(`Twitter device-follow fetch failed: ${data.detail || 'unknown error'}`);
}
if (data?.error) {
if (data.error === 401 || data.error === 403) {
throw new AuthRequiredError('x.com', `Twitter device-follow returned HTTP ${data.error}`);
}
throw new CommandExecutionError(describeTwitterApiError('device_follow', data.error));
}
const parsed = parseDeviceFollow(data, new Set());
if (!parsed) {
throw new CommandExecutionError('Twitter device-follow response was missing the expected timeline/globalObjects shape.');
}
if (parsed.malformedEntries > 0 || parsed.unmatchedTweetEntries > 0) {
throw new CommandExecutionError('Twitter device-follow entries could not be joined to tweet/user objects.');
}
if (parsed.rows.length === 0) {View on GitHub (pinned to 49907e53dc)
Solutions
- Re-run after a delay to rule out a transient rate-limit/interstitial page
- Confirm the session is authenticated (ct0 cookie present) so x.com does not redirect to a login wall
- Retry from a different IP / clear cookies to escape bot-detection challenges
- Inspect data.detail in the message to identify the specific parse failure
Defensive patterns
Strategy: retry
Validate before calling
// No pre-call check possible; verify session first to reduce login-wall risk
const cookies = await page.getCookies({ url: 'https://x.com' });
if (!cookies.some((c) => c.name === 'ct0')) throw new Error('log in first'); Type guard
function isJsonObject(text) {
try { const v = JSON.parse(text); return v !== null && typeof v === 'object'; } catch { return false; }
} Try / catch
try {
await cli.twitter.deviceFollow();
} catch (e) {
if (e instanceof CommandExecutionError && /non-JSON response/.test(e.message)) {
await sleep(30_000); // back off: likely rate-limit/interstitial page
return retryOnce();
}
throw e;
} Prevention
- Throttle request frequency to avoid x.com bot-detection interstitials
- Keep the session authenticated so x.com doesn't serve HTML login walls
- Retry with exponential backoff on non-JSON responses
- Use a clean profile without ad-blocker extensions that can intercept fetches
When it happens
Trigger: x.com returning an HTML error/interstitial page (rate-limit wall, login wall, Cloudflare challenge), a network error page, or an empty/redirect response for the device_follow GraphQL request inside the page context.
Common situations: Heavy scraping triggering rate limits or bot-detection interstitials; logged-out or suspicious sessions being redirected to HTML; temporary x.com outages returning error pages instead of GraphQL JSON.
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
- HTTP ${code}
- Response was not valid JSON: ${url}
- Server returned HTML instead of JSON (status=${response.stat
- 12306 ${endpoint} returned an unexpected payload shape
- 1point3acres request failed: ${error?.message || error}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/0de14334f03dfd66.
Report an issue: GitHub.