jackwener/OpenCLI · error · CommandExecutionError
Twitter UserMedia returned GraphQL errors: ${JSON.stringify(
Error message
Twitter UserMedia returned GraphQL errors: ${JSON.stringify(payload.errors).slice(0, 200)} What it means
The twitter download CLI calls X's GraphQL UserMedia endpoint from an authenticated browser page and validates the JSON response in requireUserMediaPayload. When the response body contains a non-empty top-level 'errors' array, the library treats the request as failed even if partial data is present, and throws this error embedding the first 200 chars of the serialized errors. This surfaces Twitter-side GraphQL rejections (bad query id, suspended user, auth/vars problems) rather than HTTP-level failures.
Source
Thrown at clis/twitter/download.js:241
const statusText = status ? `HTTP ${status}` : 'fetch failed';
throw new CommandExecutionError(`Twitter ${context} fetch failed: ${statusText}${message ? ` - ${message}` : ''}`);
}
function requireFetchPayload(value, context) {
const result = requireObjectPayload(unwrapBrowserResult(value), context);
if (result.ok === true) {
return result.payload;
}
if (result.ok === false) {
throwGraphqlFetchError(context, Number(result.status) || 0, typeof result.error === 'string' ? result.error : '');
}
throw new CommandExecutionError(`Twitter ${context} returned malformed fetch result`);
}
function requireUserMediaPayload(data) {
const payload = requireObjectPayload(data, 'UserMedia');
if (Array.isArray(payload.errors) && payload.errors.length > 0) {
throw new CommandExecutionError(`Twitter UserMedia returned GraphQL errors: ${JSON.stringify(payload.errors).slice(0, 200)}`);
}
const result = payload.data?.user?.result;
if (!result || typeof result !== 'object') {
throw new CommandExecutionError('Twitter UserMedia returned malformed user result');
}
const instructions = result.timeline_v2?.timeline?.instructions || result.timeline?.timeline?.instructions;
if (!Array.isArray(instructions)) {
throw new CommandExecutionError('Twitter UserMedia returned malformed timeline instructions');
}
return payload;
}
function parseUserMedia(data, seen) {
const items = [];
let nextCursor = null;
const result = requireUserMediaPayload(data).data.user.result;
const instructionSets = [
result.timeline_v2?.timeline?.instructions,View on GitHub (pinned to 49907e53dc)
Solutions
- Re-check the target username exists and is public (not suspended/protected); try the profile in a browser.
- Refresh the authenticated x.com session/cookies and re-run (AuthRequiredError paths suggest 401/403 elsewhere).
- Update the GraphQL queryId/endpoint used to build the UserMedia URL to the current one served by x.com.
- Inspect the embedded errors JSON in the message for the exact GraphQL error code and act on it (e.g. authorization vs not-found).
Example fix
// before
await opencli twitter download @suspendeduser --limit 10
// CommandExecutionError: Twitter UserMedia returned GraphQL errors: [{"message":"User has been suspended.",...}]
// after: pick a valid, public handle
await opencli twitter download @jack --limit 10 Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check the handle resolves and is public before invoking download
const res = await fetch('https://x.com/' + handle);
if (res.status === 404 || (res.status === 302 && res.headers.get('location')?.includes('suspended'))) {
throw new Error('Account unavailable: ' + handle);
} Type guard
function isGraphqlErrorFree(payload) {
return !!payload && typeof payload === 'object' &&
(!Array.isArray(payload.errors) || payload.errors.length === 0);
} Try / catch
try {
await twitterDownload(username);
} catch (err) {
if (err instanceof CommandExecutionError && err.message.includes('returned GraphQL errors')) {
// surface payload.errors detail, refresh session or update queryId, then retry
console.error('UserMedia GraphQL rejected the request:', err.message);
} else throw err;
} Prevention
- Keep the x.com session cookies fresh before running downloads
- Verify target accounts are public and not suspended beforehand
- Update the UserMedia GraphQL queryId whenever X rotates its frontend build
- Read the embedded errors JSON in the message to distinguish auth vs not-found causes
When it happens
Trigger: The UserMedia GraphQL response JSON contains payload.errors as a non-empty array; e.g. the queryId in the GraphQL URL is stale/rotated, the target user is suspended or protected, variables are rejected, or the session cookies are valid enough to fetch but insufficient for the endpoint.
Common situations: X rotated the UserMedia GraphQL query id so the endpoint returns errors; scraping a suspended/protected account; expired or partial auth cookies; running without a logged-in x.com session behind the COOKIE strategy.
Related errors
- Twitter UserMedia returned malformed user result
- Twitter UserMedia returned malformed timeline instructions
- Twitter UserMedia returned a tweet without rest_id
- ${probe.detail}
- douyin hashtag ${action}: API returned malformed payload
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/dbbb5dc6bf288213.
Report an issue: GitHub.