jackwener/OpenCLI · error · CommandExecutionError

CreateList failed: ${errors[0].message || JSON.stringify(err

Error message

CreateList failed: ${errors[0].message || JSON.stringify(errors[0])}

What it means

This CommandExecutionError is thrown by requireCreateListResult in clis/twitter/list-create.js:58 when the CreateList response is valid JSON with ok status, but contains no data.list object and instead carries a GraphQL errors array. The library surfaces errors[0].message (or its JSON) verbatim. It means Twitter's GraphQL layer rejected the mutation even though the HTTP transport succeeded.

Source

Thrown at clis/twitter/list-create.js:58

function requireCreateListResult(result, expectedName, expectedMode) {
    if (!result || typeof result !== 'object') {
        throw new CommandExecutionError(`Unexpected result from twitter list-create: ${JSON.stringify(result)}`);
    }
    if (result.httpStatus === 401 || result.httpStatus === 403) {
        throw new AuthRequiredError('x.com', `Twitter CreateList returned HTTP ${result.httpStatus}`);
    }
    if (!result.ok) {
        const snippet = String(result.bodyText || '').slice(0, 300);
        throw new CommandExecutionError(`HTTP ${result.httpStatus} from CreateList: ${snippet}`);
    }
    if (!result.bodyJson || typeof result.bodyJson !== 'object') {
        throw new CommandExecutionError(`CreateList returned malformed JSON payload. Body: ${String(result.bodyText || '').slice(0, 300)}`);
    }
    const list = result.bodyJson?.data?.list;
    if (!list || typeof list !== 'object') {
        const errors = result.bodyJson?.errors;
        if (Array.isArray(errors) && errors.length > 0) {
            throw new CommandExecutionError(`CreateList failed: ${errors[0].message || JSON.stringify(errors[0])}`);
        }
        throw new CommandExecutionError(`CreateList returned no list payload. Body: ${String(result.bodyText || '').slice(0, 300)}`);
    }
    const id = String(list.id_str || list.id || '');
    if (!/^\d+$/.test(id)) {
        throw new CommandExecutionError('CreateList returned a list payload without a numeric list id.');
    }
    if (typeof list.name !== 'string' || !list.name.trim()) {
        throw new CommandExecutionError('CreateList returned a list payload without a list name.');
    }
    if (list.name.trim() !== expectedName) {
        throw new CommandExecutionError(`CreateList returned name ${JSON.stringify(list.name)}, expected ${JSON.stringify(expectedName)}.`);
    }
    const modeValue = typeof list.mode === 'string' ? list.mode : '';
    if (!modeValue) {
        throw new CommandExecutionError('CreateList returned a list payload without list mode.');
    }
    const mode = /private/i.test(modeValue) ? 'private' : 'public';

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the surfaced errors[0].message — it names the actual GraphQL failure (e.g. DecodeException names the offending field).
  2. If it is a DecodeException, update FEATURES in clis/twitter/list-create.js to exactly match the current web client's CreateList feature set (extra/unknown features are rejected).
  3. Ensure CREATE_LIST_QUERY_ID and FEATURES come from the same web client version — a mismatched pair triggers schema errors.
  4. Check the account can create lists (not suspended/restricted) and the name/description satisfy Twitter's constraints.
  5. Retry once before debugging deeply — Twitter sometimes returns non-fatal transient error arrays; the library itself checks for a valid list first, so a fatal throw here is a real rejection.

Example fix

// before (features drift vs current schema)
const FEATURES = { responsive_web_graphql_timeline_navigation_enabled: true, ... };
// after (capture the exact features payload from a live CreateList request in the browser devtools)
const FEATURES = { /* current exact feature set from live x.com request */ };
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate what the GraphQL layer most commonly rejects:
const name = String(kwargs.name || '').trim();
if (!name || name.length > 25) throw new Error('invalid list name');
if (String(kwargs.description || '').trim().length > 100) throw new Error('description too long');
if (!['public','private'].includes(String(kwargs.mode||'public').toLowerCase())) throw new Error('invalid mode');

Type guard

function hasGraphqlErrors(bodyJson) {
  return Array.isArray(bodyJson?.errors) && bodyJson.errors.length > 0;
}

Try / catch

try {
  const row = await opencli twitter list-create name;
} catch (err) {
  if (/^CreateList failed:/.test(err.message)) {
    const reason = err.message.slice('CreateList failed:'.length).trim();
    if (/DecodeException|decode/i.test(reason)) updateQueryIdAndFeatures();
    else reportFatal(reason);
  } else throw err;
}

Prevention

When it happens

Trigger: Response shape { errors: [ { message: ... } ] } with no data.list: e.g. schema DecodeException from a features/variables mismatch with the queryId schema, authorization/GraphQL-level denial, or duplicate/invalid list parameters rejected at the GraphQL layer.

Common situations: A newer x.com client expects different `features` than the hardcoded FEATURES map (strato DecodeException); the hardcoded queryId no longer matches the features schema; the account lacks permission to create lists (restricted/suspended); duplicate list name constraints enforced GraphQL-side.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/802822ce62da58c3. Report an issue: GitHub.