{"record":{"id":"e1a6ef2c8caf95c1","repo":"jackwener/OpenCLI","slug":"could-not-fetch-lists-listsdata-error-e1a6ef","errorCode":null,"errorMessage":"Could not fetch lists: ${listsData.__error}","messagePattern":"Could not fetch lists: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-remove-core.js","lineNumber":94,"sourceCode":"        const userLookupUrl = buildUserByScreenNameQueryUrl(userByScreenNameQueryId, username);\n        const userId = unwrapBrowserResult(await page.evaluate(`async () => {\n            const resp = await fetch(${JSON.stringify(userLookupUrl)}, { headers: ${headers}, credentials: 'include' });\n            if (!resp.ok) return null;\n            const d = await resp.json();\n            return d.data?.user?.result?.rest_id || null;\n        }`));\n        if (!userId) throw new CommandExecutionError(`Could not resolve user @${username}`);\n\n        // Resolve listId → name so we can match the dialog row.\n        const listsQueryId = await resolveTwitterQueryId(page, 'ListsManagementPageTimeline', LISTS_MANAGEMENT_QUERY_ID);\n        const listsUrl = `/i/api/graphql/${listsQueryId}/ListsManagementPageTimeline?features=${encodeURIComponent(JSON.stringify(LISTS_MANAGEMENT_FEATURES))}`;\n        const listsData = unwrapBrowserResult(await page.evaluate(`async () => {\n            const r = await fetch(${JSON.stringify(listsUrl)}, { headers: ${headers}, credentials: 'include' });\n            if (!r.ok) return { __error: 'HTTP ' + r.status };\n            return await r.json();\n        }`));\n        if (listsData && listsData.__error) {\n            throw new CommandExecutionError(`Could not fetch lists: ${listsData.__error}`);\n        }\n        const parsedLists = parseListsManagement(listsData, new Set());\n        const targetList = parsedLists.find((l) => l.id === listId);\n        if (!targetList) {\n            throw new CommandExecutionError(`List ${listId} not found among your lists.`);\n        }\n        const targetName = targetList.name;\n\n        await page.goto(`https://x.com/${username}`);\n        await page.wait({ selector: '[data-testid=\"primaryColumn\"]' });\n        const uiResult = unwrapBrowserResult(await page.evaluate(`(async () => {\n            const sleep = (ms) => new Promise(r => setTimeout(r, ms));\n            const findOne = (sel, root = document) => root.querySelector(sel);\n            const waitFor = async (fn, { timeoutMs = 8000, intervalMs = 200 } = {}) => {\n                const t0 = Date.now();\n                while (Date.now() - t0 < timeoutMs) { const v = fn(); if (v) return v; await sleep(intervalMs); }\n                return null;\n            };","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-remove-core.js#L76-L112","documentation":"After resolving the user, listRemoveUser fetches the ListsManagementPageTimeline GraphQL timeline inside the page. Any non-OK HTTP response is converted to { __error: 'HTTP <status>' } and surfaced as this CommandExecutionError. It means the lists-management timeline could not be downloaded, so the target list cannot be located.","triggerScenarios":"The in-page fetch of /i/api/graphql/<queryId>/ListsManagementPageTimeline returns a non-2xx status — typically 401/403 from an expired or missing ct0/auth session, 429 rate limiting, 400 from stale/rotated queryId or features object, or 5xx from Twitter.","commonSituations":"Session cookies expired mid-run; Twitter rotated the ListsManagementPageTimeline query ID so the hardcoded one 400s; the LISTS_MANAGEMENT_FEATURES blob no longer satisfies Twitter's feature-flag validation; aggressive automation triggered 429s; transient Twitter 5xx.","solutions":["Re-login to x.com in the controlled browser to refresh the auth session, then retry.","Check the HTTP status embedded in the message: 401/403 → session problem, 429 → back off and retry later, 400 → stale queryId/features.","Update LISTS_MANAGEMENT_QUERY_ID and LISTS_MANAGEMENT_FEATURES to match the current x.com web app payloads.","Retry after a delay if the status is 429 or 5xx (rate limiting or Twitter-side outage).","Confirm the account is logged in and has not been flagged for automation (some restrictions return 403 on GraphQL endpoints)."],"exampleFix":"// before (stale hardcoded query id → HTTP 400)\nconst LISTS_MANAGEMENT_QUERY_ID = '78UbkyXwXBD98IgUWXOy9g';\n// after\nconst LISTS_MANAGEMENT_QUERY_ID = await resolveTwitterQueryId(page, 'ListsManagementPageTimeline', LISTS_MANAGEMENT_QUERY_ID_FALLBACK); // resolved live from page","handlingStrategy":"retry","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nif (!cookies.some(c => c.name === 'ct0')) throw new Error('Login to x.com before running list-remove');","typeGuard":"function listsFetchedOk(d) {\n  return typeof d === 'object' && d !== null && d.__error === undefined;\n}","tryCatchPattern":"try {\n  await listRemoveUser(page, { listId, username });\n} catch (e) {\n  if (/Could not fetch lists: HTTP (429|5\\d\\d)/.test(e.message)) {\n    await sleep(60_000); // back off, then retry\n  } else throw e;\n}","preventionTips":["Re-login to x.com whenever sessions get stale.","Keep the queryId and features constants in sync with the current x.com web app.","Rate-limit your automation to avoid 429s.","Distinguish status codes in handling: 401/403 = session, 400 = schema, 429/5xx = retry."],"tags":["twitter","graphql","http-error","authentication","rate-limit"],"backgroundTag":"graphql-http-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}