{"record":{"id":"1425b8cda28e114a","repo":"jackwener/OpenCLI","slug":"malformed-twitter-follower-missing-screen-name","errorCode":null,"errorMessage":"Malformed Twitter follower: missing screen_name","messagePattern":"Malformed Twitter follower: missing screen_name","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/followers.js","lineNumber":15,"sourceCode":"import { ArgumentError, AuthRequiredError, EmptyResultError, CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';\nimport { cli, Strategy } from '@jackwener/opencli/registry';\nimport { looksLikePrivateTwitterTimeline, normalizeTwitterGraphqlPayload, normalizeTwitterScreenName, unwrapBrowserResult } from './shared.js';\n\nconst MAX_PAGINATION_PAGES = 100;\nconst CAPTURE_TIMEOUT_SECONDS = 10;\n\nfunction extractFollower(result) {\n    if (!result || result.__typename !== 'User')\n        return null;\n    const core = result.core || {};\n    const legacy = result.legacy || {};\n    const screenName = core.screen_name || legacy.screen_name || '';\n    if (!screenName) {\n        throw new CommandExecutionError('Malformed Twitter follower: missing screen_name');\n    }\n    return {\n        screen_name: screenName,\n        name: core.name || legacy.name || '',\n        bio: result.profile_bio?.description || legacy.description || '',\n    };\n}\n\nfunction parseFollowers(value) {\n    const data = normalizeTwitterGraphqlPayload(value);\n    const users = [];\n    let nextCursor = null;\n    let bottomTerminated = false;\n    const instructions = data?.data?.user?.result?.timeline_v2?.timeline?.instructions\n        || data?.data?.user?.result?.timeline?.timeline?.instructions\n        || [];\n    for (const instruction of instructions) {\n        if (instruction?.type === 'TimelineTerminateTimeline' && instruction.direction === 'Bottom') {","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/followers.js#L1-L33","documentation":"This error is thrown by extractFollower when a follower entry parsed from Twitter's Followers GraphQL timeline has __typename 'User' but no screen_name in either the core or legacy blocks. The library treats a User entry without a handle as malformed because the screen_name column is the primary key of the output contract (columns: ['screen_name','name','bio']); it cannot emit a dedupable row without it. It surfaces as a CommandExecutionError rather than silently skipping the entry.","triggerScenarios":"A Followers GraphQL response contains a user- entry whose itemContent.user_results.result has __typename 'User' but both core.screen_name and legacy.screen_name are absent/empty — typically when Twitter ships a new GraphQL payload shape or returns a partially redacted/suspended user object.","commonSituations":"Twitter/X changes its GraphQL response schema (A/B tests, API versions); a follower account is suspended or restricted so fields are stripped; a proxy or mock returns truncated user objects during testing.","solutions":["Update the library / shared.js payload normalization to the current Twitter GraphQL shape so core.screen_name or legacy.screen_name is found","Re-run the command — transient malformed entries often disappear on retry","Check whether the target account is suspended/restricted and try a different target user","File a bug with the raw captured response so the parser can be extended"],"exampleFix":"// before\nconst screenName = core.screen_name || legacy.screen_name || '';\nif (!screenName) {\n    throw new CommandExecutionError('Malformed Twitter follower: missing screen_name');\n}\n// after (skip instead of failing the whole run)\nconst screenName = core.screen_name || legacy.screen_name || result.screen_name || '';\nif (!screenName) return null;","handlingStrategy":"try-catch","validationCode":"const result = entry?.itemContent?.user_results?.result;\nif (result?.__typename === 'User' && !(result.core?.screen_name || result.legacy?.screen_name)) {\n  console.warn('skipping follower without screen_name');\n}","typeGuard":"function hasScreenName(r) {\n  return !!r && r.__typename === 'User' &&\n    typeof (r.core?.screen_name || r.legacy?.screen_name) === 'string' &&\n    (r.core?.screen_name || r.legacy?.screen_name).length > 0;\n}","tryCatchPattern":"try {\n  const rows = await opencli.twitter.followers(user, { limit });\n} catch (err) {\n  if (err.message.includes('Malformed Twitter follower')) {\n    // schema drift: retry, update library, or fall back to partial data\n  } else throw err;\n}","preventionTips":["Keep the library updated against current X GraphQL payloads","Prefer skipping malformed entries over failing whole runs when tolerable","Test parsers against recorded real API responses"],"tags":["twitter","graphql","schema-change","parsing"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}