jackwener/OpenCLI · error · CommandExecutionError
device_follow
Error message
device_follow
What it means
For any other GraphQL error status (not 401/403, not a parse/exception failure), the command builds a human-readable message via describeTwitterApiError('device_follow', data.error) and throws CommandExecutionError. The raw message 'device_follow' is the API name passed into that describer, so the thrown text summarizes an API-level error for the device_follow endpoint.
Source
Thrown at clis/twitter/device-follow.js:169
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) {
throw new EmptyResultError('twitter device-follow', 'No device-follow notification tweets found.');
}
const rows = parsed.rows;
const trimmed = rows.slice(0, limit);
return applyTopByEngagement(trimmed, kwargs['top-by-engagement']);
},
});
export const __test__ = {View on GitHub (pinned to 49907e53dc)
Solutions
- Read the full describeTwitterApiError message for the specific code
- Back off and retry with lower frequency if the code indicates rate limiting (429)
- Check whether the endpoint was changed/deprecated and update the CLI
- Retry later for 5xx codes — often a Twitter-side incident
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check not possible for API-side codes; rate-limit friendly pacing instead const sleepMs = Number(process.env.TWITTER_MIN_INTERVAL_MS || 2000);
Try / catch
try {
await cli.twitter.deviceFollow();
} catch (e) {
if (e instanceof CommandExecutionError && e.message.includes('429')) {
await sleep(15 * 60_000); // rate limited: long backoff
return retryOnce();
}
if (e instanceof CommandExecutionError) throw e; // other API codes: inspect message
throw e;
} Prevention
- Space out x.com API calls to avoid 429 rate limits
- Log the full describeTwitterApiError message to identify the code
- Watch for x.com endpoint deprecations and keep the CLI updated
- Treat 5xx codes as transient and retry later
When it happens
Trigger: The device_follow GraphQL endpoint returns a JSON body with an error field whose code/status is anything other than 401 or 403 (e.g. 404, 429 rate limit, 5xx, or a GraphQL errors array code).
Common situations: Hitting rate limits (429) during heavy scraping; Twitter deprecating or relocating the device_follow endpoint (404); transient 5xx server errors; account restrictions returning unusual codes.
Related errors
- Bookmarks
- twitter_collection_request_error
- Likes
- Could not fetch lists: ${listsData.__error}
- ${describeTwitterApiError('SearchTimeline', data.error)}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/1d1c49d62ba4c5ec.
Report an issue: GitHub.