{"record":{"id":"dbbb5dc6bf288213","repo":"jackwener/OpenCLI","slug":"twitter-usermedia-returned-graphql-errors-json","errorCode":null,"errorMessage":"Twitter UserMedia returned GraphQL errors: ${JSON.stringify(payload.errors).slice(0, 200)}","messagePattern":"Twitter UserMedia returned GraphQL errors: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/download.js","lineNumber":241,"sourceCode":"    const statusText = status ? `HTTP ${status}` : 'fetch failed';\n    throw new CommandExecutionError(`Twitter ${context} fetch failed: ${statusText}${message ? ` - ${message}` : ''}`);\n}\n\nfunction requireFetchPayload(value, context) {\n    const result = requireObjectPayload(unwrapBrowserResult(value), context);\n    if (result.ok === true) {\n        return result.payload;\n    }\n    if (result.ok === false) {\n        throwGraphqlFetchError(context, Number(result.status) || 0, typeof result.error === 'string' ? result.error : '');\n    }\n    throw new CommandExecutionError(`Twitter ${context} returned malformed fetch result`);\n}\n\nfunction requireUserMediaPayload(data) {\n    const payload = requireObjectPayload(data, 'UserMedia');\n    if (Array.isArray(payload.errors) && payload.errors.length > 0) {\n        throw new CommandExecutionError(`Twitter UserMedia returned GraphQL errors: ${JSON.stringify(payload.errors).slice(0, 200)}`);\n    }\n    const result = payload.data?.user?.result;\n    if (!result || typeof result !== 'object') {\n        throw new CommandExecutionError('Twitter UserMedia returned malformed user result');\n    }\n    const instructions = result.timeline_v2?.timeline?.instructions || result.timeline?.timeline?.instructions;\n    if (!Array.isArray(instructions)) {\n        throw new CommandExecutionError('Twitter UserMedia returned malformed timeline instructions');\n    }\n    return payload;\n}\n\nfunction parseUserMedia(data, seen) {\n    const items = [];\n    let nextCursor = null;\n    const result = requireUserMediaPayload(data).data.user.result;\n    const instructionSets = [\n        result.timeline_v2?.timeline?.instructions,","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/download.js#L223-L259","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nawait opencli twitter download @suspendeduser --limit 10\n// CommandExecutionError: Twitter UserMedia returned GraphQL errors: [{\"message\":\"User has been suspended.\",...}]\n\n// after: pick a valid, public handle\nawait opencli twitter download @jack --limit 10","handlingStrategy":"try-catch","validationCode":"// Pre-check the handle resolves and is public before invoking download\nconst res = await fetch('https://x.com/' + handle);\nif (res.status === 404 || (res.status === 302 && res.headers.get('location')?.includes('suspended'))) {\n    throw new Error('Account unavailable: ' + handle);\n}","typeGuard":"function isGraphqlErrorFree(payload) {\n    return !!payload && typeof payload === 'object' &&\n        (!Array.isArray(payload.errors) || payload.errors.length === 0);\n}","tryCatchPattern":"try {\n    await twitterDownload(username);\n} catch (err) {\n    if (err instanceof CommandExecutionError && err.message.includes('returned GraphQL errors')) {\n        // surface payload.errors detail, refresh session or update queryId, then retry\n        console.error('UserMedia GraphQL rejected the request:', err.message);\n    } else throw err;\n}","preventionTips":["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"],"tags":["twitter","graphql","api-response","scraping"],"backgroundTag":"graphql-errors-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}