{"record":{"id":"5e1e5793625f3cdc","repo":"jackwener/OpenCLI","slug":"twitter-device-follow-returned-http-data-error","errorCode":null,"errorMessage":"Twitter device-follow returned HTTP ${data.error}","messagePattern":"Twitter device-follow returned HTTP (.+?)","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/twitter/device-follow.js","lineNumber":167,"sourceCode":"          if (!r.ok) return { error: r.status };\n          try {\n            return await r.json();\n          } catch (e) {\n            return { errorKind: 'non_json', detail: String(e && e.message || e) };\n          }\n        } 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});","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/device-follow.js#L149-L185","documentation":"When the GraphQL response parses but carries an error status of 401 or 403, the command throws AuthRequiredError for x.com with the HTTP code embedded. This means the session's credentials were rejected by the API rather than the response being malformed.","triggerScenarios":"The device_follow API responds with HTTP 401 (expired/invalid auth token or csrf mismatch) or 403 (forbidden — suspended account, bot detection, or token/cookie pair out of sync).","commonSituations":"Stale bearer token after x.com rotated keys; ct0 cookie not matching the session (copied cookies from another browser); account flagged or suspended; IP flagged by Twitter's anti-bot system.","solutions":["Log out and back into x.com to refresh the session and ct0 cookie","Clear x.com cookies in the CLI-managed profile and re-authenticate","Verify the whole cookie jar comes from the same session (don't mix ct0 from one profile with another session)","If 403 persists on a clean session, the account/IP may be flagged — wait or switch networks"],"exampleFix":"// before (stale cookies)\ncli twitter device-follow  # AuthRequiredError: HTTP 401\n// after\n# clear x.com cookies in the managed profile, log in again, then\ncli twitter device-follow","handlingStrategy":"fallback","validationCode":"const cookies = await page.getCookies({ url: 'https://x.com' });\nconst ok = cookies.some((c) => c.name === 'ct0' && c.value);\n// also ensure cookies all come from one session/profile","typeGuard":null,"tryCatchPattern":"try {\n  await cli.twitter.deviceFollow();\n} catch (e) {\n  if (e instanceof AuthRequiredError && /HTTP 40[13]/.test(e.message)) {\n    console.error('Session rejected — clear x.com cookies and log in again.');\n    return await reauthenticateThen(fn);\n  }\n  throw e;\n}","preventionTips":["Re-login after password changes; old sessions get 401","Keep the ct0 cookie and auth token from the same browser session","Switch IP / avoid datacenter IPs to reduce 403 bot-flag risk","Rotate re-authentication routinely for long-running scrapers"],"tags":["auth","http-401","http-403","session"],"backgroundTag":"auth-required-http-403","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}