jackwener/OpenCLI · error · CommandExecutionError

Twitter UserMedia returned malformed timeline instructions

Error message

Twitter UserMedia returned malformed timeline instructions

What it means

After validating the user result, requireUserMediaPayload expects a timeline instructions array at result.timeline_v2.timeline.instructions or the legacy result.timeline.timeline.instructions path. If neither is an array, it throws 'malformed timeline instructions'. This protects the recursive parser (parseUserMedia/visit) from walking a structure with no entries to visit.

Source

Thrown at clis/twitter/download.js:249

    }
    if (result.ok === false) {
        throwGraphqlFetchError(context, Number(result.status) || 0, typeof result.error === 'string' ? result.error : '');
    }
    throw new CommandExecutionError(`Twitter ${context} returned malformed fetch result`);
}

function requireUserMediaPayload(data) {
    const payload = requireObjectPayload(data, 'UserMedia');
    if (Array.isArray(payload.errors) && payload.errors.length > 0) {
        throw new CommandExecutionError(`Twitter UserMedia returned GraphQL errors: ${JSON.stringify(payload.errors).slice(0, 200)}`);
    }
    const result = payload.data?.user?.result;
    if (!result || typeof result !== 'object') {
        throw new CommandExecutionError('Twitter UserMedia returned malformed user result');
    }
    const instructions = result.timeline_v2?.timeline?.instructions || result.timeline?.timeline?.instructions;
    if (!Array.isArray(instructions)) {
        throw new CommandExecutionError('Twitter UserMedia returned malformed timeline instructions');
    }
    return payload;
}

function parseUserMedia(data, seen) {
    const items = [];
    let nextCursor = null;
    const result = requireUserMediaPayload(data).data.user.result;
    const instructionSets = [
        result.timeline_v2?.timeline?.instructions,
        result.timeline?.timeline?.instructions,
    ].filter(Array.isArray);
    const instructions = instructionSets.flat();
    const visit = (value) => {
        if (!value || typeof value !== 'object') return;
        if (value.type === 'TimelinePinEntry') return;
        if (value.tweet_results?.result) {
            const raw = value.tweet_results.result;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check the latest x.com UserMedia GraphQL response in browser devtools and update the instructions lookup path (timeline_v2 vs timeline) in requireUserMediaPayload.
  2. Re-run after confirming the account actually has media; a truly empty timeline may need an explicit empty-result branch instead of an error.
  3. Verify the queryId matches the current x.com build — mismatched query ids can yield truncated/alternate payloads.
  4. Add support for any new container field X introduced (e.g. additional result.* timeline variants).

Example fix

// before
const instructions = result.timeline_v2?.timeline?.instructions || result.timeline?.timeline?.instructions;

// after: tolerate additional/new container paths
const instructions = result.timeline_v2?.timeline?.instructions
    || result.timeline?.timeline?.instructions
    || result.timeline_response?.timeline?.instructions;
Defensive patterns

Strategy: validation

Validate before calling

// Probe the endpoint shape before full parsing
const instructions = result?.timeline_v2?.timeline?.instructions ?? result?.timeline?.timeline?.instructions;
if (!Array.isArray(instructions)) {
    console.warn('UserMedia timeline shape unrecognized — update parser');
}

Type guard

function hasTimelineInstructions(result) {
    return Array.isArray(result?.timeline_v2?.timeline?.instructions)
        || Array.isArray(result?.timeline?.timeline?.instructions);
}

Try / catch

try {
    await twitterDownload(username);
} catch (err) {
    if (err instanceof CommandExecutionError && err.message.includes('malformed timeline instructions')) {
        // pin/upgrade the parser to the current X GraphQL schema, then retry
    } else throw err;
}

Prevention

When it happens

Trigger: The user result object exists but contains no timeline_v2/timeline instructions — e.g. the account has no media timeline payload, X changed the response schema (timeline_v2 renamed/moved), or the entry has an empty/unexpected node shape.

Common situations: X shipping a GraphQL schema change to UserMedia (very common with timeline_v2 vs timeline migrations); new account with no tweets/media returning a different node; A/B-tested response variants for some sessions.

Understand the failure class

Related errors


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