{"record":{"id":"108773aa36d4d6f1","repo":"jackwener/OpenCLI","slug":"twitter-device-follow-response-was-missing-the-exp","errorCode":null,"errorMessage":"Twitter device-follow response was missing the expected timeline/globalObjects shape.","messagePattern":"Twitter device-follow response was missing the expected timeline/globalObjects shape\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/device-follow.js","lineNumber":173,"sourceCode":"        } catch (e) {\n          return { errorKind: 'exception', detail: String(e && e.message || e) };\n        }\n      }`);\n        if (data?.errorKind === 'non_json') {\n            throw new CommandExecutionError(`Twitter device-follow returned non-JSON response: ${data.detail || 'unknown parse error'}`);\n        }\n        if (data?.errorKind === 'exception') {\n            throw new CommandExecutionError(`Twitter device-follow fetch failed: ${data.detail || 'unknown error'}`);\n        }\n        if (data?.error) {\n            if (data.error === 401 || data.error === 403) {\n                throw new AuthRequiredError('x.com', `Twitter device-follow returned HTTP ${data.error}`);\n            }\n            throw new CommandExecutionError(describeTwitterApiError('device_follow', data.error));\n        }\n        const parsed = parseDeviceFollow(data, new Set());\n        if (!parsed) {\n            throw new CommandExecutionError('Twitter device-follow response was missing the expected timeline/globalObjects shape.');\n        }\n        if (parsed.malformedEntries > 0 || parsed.unmatchedTweetEntries > 0) {\n            throw new CommandExecutionError('Twitter device-follow entries could not be joined to tweet/user objects.');\n        }\n        if (parsed.rows.length === 0) {\n            throw new EmptyResultError('twitter device-follow', 'No device-follow notification tweets found.');\n        }\n        const rows = parsed.rows;\n        const trimmed = rows.slice(0, limit);\n        return applyTopByEngagement(trimmed, kwargs['top-by-engagement']);\n    },\n});\n\nexport const __test__ = {\n    buildDeviceFollowUrl,\n    extractEntries,\n    joinEntryToTweet,\n    shapeRow,","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/device-follow.js#L155-L191","documentation":"After a successful fetch, the command runs parseDeviceFollow to extract tweets from the timeline/globalObjects structure. If that shape is absent (parser returns falsy), it throws CommandExecutionError because x.com returned JSON that does not match the expected device-follow notification payload.","triggerScenarios":"The GraphQL endpoint responds 200 with JSON that lacks the timeline/globalObjects envelope — e.g. an empty body, a different instruction format after a Twitter frontend change, or a soft-error JSON object without the usual error field.","commonSituations":"Twitter silently changing its GraphQL response schema; a logged-in-but-restricted session returning an alternate payload; hitting a variant endpoint that returns a new response format the parser doesn't know.","solutions":["Update/reinstall the CLI in case a newer version parses the new schema","Dump the raw response and compare it against the expected timeline/globalObjects shape","Confirm the authenticated account actually has device-follow notifications enabled","Check for reports of x.com API schema changes if this starts failing consistently"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// No caller-side pre-check; ensure auth first so the payload is the real one\nconst cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some((c) => c.name === 'ct0')) throw new Error('log in first');","typeGuard":"function hasTimelineShape(data) {\n  return !!data && typeof data === 'object' &&\n    ('timeline' in data || 'globalObjects' in data);\n}","tryCatchPattern":"try {\n  await cli.twitter.deviceFollow();\n} catch (e) {\n  if (e instanceof CommandExecutionError && /timeline\\/globalObjects shape/.test(e.message)) {\n    console.error('x.com response schema changed — update the CLI.');\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["Keep the CLI updated — x.com changes its GraphQL payload format over time","Capture the raw response when debugging to diff against the expected shape","Confirm the account has device-follow data before assuming parser failure","Report persistent schema errors upstream with the raw payload"],"tags":["schema","parsing","twitter","api-change"],"backgroundTag":"unexpected-response-schema","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}