{"record":{"id":"174bb95ff70b321d","repo":"jackwener/OpenCLI","slug":"http-result-httpstatus-from-createlist-snipp","errorCode":null,"errorMessage":"HTTP ${result.httpStatus} from CreateList: ${snippet}","messagePattern":"HTTP (.+?) from CreateList: (.+?)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/list-create.js","lineNumber":49,"sourceCode":"    if (description.length > DESCRIPTION_MAX) {\n        throw new ArgumentError(`Description too long: ${description.length} chars (max ${DESCRIPTION_MAX})`);\n    }\n    if (modeRaw !== 'public' && modeRaw !== 'private') {\n        throw new ArgumentError(`Invalid mode: ${JSON.stringify(kwargs.mode)}. Expected \"public\" or \"private\".`);\n    }\n    return { listName: name, listDescription: description, listMode: modeRaw, privateFlag: modeRaw === 'private' };\n}\n\nfunction requireCreateListResult(result, expectedName, expectedMode) {\n    if (!result || typeof result !== 'object') {\n        throw new CommandExecutionError(`Unexpected result from twitter list-create: ${JSON.stringify(result)}`);\n    }\n    if (result.httpStatus === 401 || result.httpStatus === 403) {\n        throw new AuthRequiredError('x.com', `Twitter CreateList returned HTTP ${result.httpStatus}`);\n    }\n    if (!result.ok) {\n        const snippet = String(result.bodyText || '').slice(0, 300);\n        throw new CommandExecutionError(`HTTP ${result.httpStatus} from CreateList: ${snippet}`);\n    }\n    if (!result.bodyJson || typeof result.bodyJson !== 'object') {\n        throw new CommandExecutionError(`CreateList returned malformed JSON payload. Body: ${String(result.bodyText || '').slice(0, 300)}`);\n    }\n    const list = result.bodyJson?.data?.list;\n    if (!list || typeof list !== 'object') {\n        const errors = result.bodyJson?.errors;\n        if (Array.isArray(errors) && errors.length > 0) {\n            throw new CommandExecutionError(`CreateList failed: ${errors[0].message || JSON.stringify(errors[0])}`);\n        }\n        throw new CommandExecutionError(`CreateList returned no list payload. Body: ${String(result.bodyText || '').slice(0, 300)}`);\n    }\n    const id = String(list.id_str || list.id || '');\n    if (!/^\\d+$/.test(id)) {\n        throw new CommandExecutionError('CreateList returned a list payload without a numeric list id.');\n    }\n    if (typeof list.name !== 'string' || !list.name.trim()) {\n        throw new CommandExecutionError('CreateList returned a list payload without a list name.');","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/list-create.js#L31-L67","documentation":"This CommandExecutionError is thrown by requireCreateListResult in clis/twitter/list-create.js:49 when the CreateList GraphQL POST returned a non-OK HTTP status that is not 401/403 (those become AuthRequiredError instead). The message embeds the status code and the first 300 chars of the response body so the developer can see Twitter's own error payload. It means Twitter rejected the list-creation request at the HTTP level — the CLI never got far enough to parse a GraphQL result.","triggerScenarios":"Any POST to /i/api/graphql/UQRa0jJ9doxGEIQRea1Y0w/CreateList from page.evaluate that resolves with r.ok === false and status not in {401, 403}: e.g. 400 (malformed variables/features), 404 (queryId no longer served), 429 (rate limited), 5xx (Twitter server error).","commonSituations":"Twitter rotating the CreateList queryId so the hardcoded UQRa0jJ9doxGEIQRea1Y0w becomes stale (404); a schema change making the hardcoded FEATURES set invalid (400 DecodeException); hitting Twitter's write rate limits after creating several lists (429); transient Twitter incidents (5xx); expired/blocked session producing odd non-401 statuses.","solutions":["Read the embedded body snippet in the message — it contains Twitter's JSON error explaining the rejection (e.g. rate limit, DecodeException).","If status is 404 or the body mentions queryId/schema mismatch, update CREATE_LIST_QUERY_ID and FEATURES in clis/twitter/list-create.js to match the current x.com web client.","If status is 429, wait and retry later — you have hit Twitter's list-creation rate limit.","If status is 400, verify the request variables (name <= 25 chars, description <= 100 chars, valid mode) — use --mode public|private exactly.","If status is 5xx, retry after a short delay; it is a transient Twitter-side failure.","Confirm you are logged in to x.com in the automation browser session (stale cookies can cause unusual non-401 rejections)."],"exampleFix":"// before (drifted queryId causing 404)\nconst CREATE_LIST_QUERY_ID = 'UQRa0jJ9doxGEIQRea1Y0w';\n// after (refreshed from the current x.com web client CreateList request)\nconst CREATE_LIST_QUERY_ID = 'UQRa0jJ9doxGEIQRea1Y0w_newIdFromLiveCapture';","handlingStrategy":"try-catch","validationCode":"// Nothing pre-call can fully prevent server-side rejections, but validate inputs and session first:\nif (!name || name.length > 25) throw new Error('List name must be 1-25 chars');\nif (description.length > 100) throw new Error('Description must be <= 100 chars');\nif (mode !== 'public' && mode !== 'private') throw new Error('mode must be public|private');\n// ensure browser session is logged in (ct0 cookie exists) before invoking","typeGuard":"function isCreateListHttpError(err) {\n  return err instanceof Error\n    && err.name === 'CommandExecutionError'\n    && /^HTTP \\d{3} from CreateList:/.test(err.message);\n}","tryCatchPattern":"try {\n  const row = await opencli twitter list-create \"My List\";\n} catch (err) {\n  if (/AuthRequiredError/.test(err.name)) { /* re-authenticate x.com */ }\n  else if (isCreateListHttpError(err)) {\n    const status = Number(err.message.match(/^HTTP (\\d{3})/)?.[1]);\n    if (status === 429) scheduleRetryWithBackoff();\n    else if (status >= 500) retryOnceLater();\n    else reportFatal(err.message); // 4xx: body snippet has Twitter's reason\n  } else throw err;\n}","preventionTips":["Keep CREATE_LIST_QUERY_ID and FEATURES in sync with the current x.com web client to avoid 400/404 rejections","Respect Twitter write rate limits — space out list creations","Always read the body snippet embedded in the message before guessing at the cause","Validate name/description/mode arguments before invoking (parseListCreateArgs already enforces limits)"],"tags":["http","twitter","api-error","graphql"],"backgroundTag":"http-4xx-from-api-endpoint","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}