jackwener/OpenCLI · error · CommandExecutionError

Twitter tweet media extraction returned malformed payload

Error message

Twitter tweet media extraction returned malformed payload

What it means

downloadSingleTweet scrapes media by evaluating DOM queries in the page and unwrapping the result with unwrapBrowserResult. If the evaluated result is not an array (browser evaluation failed, returned an error wrapper, or was null), the command throws CommandExecutionError 'Twitter tweet media extraction returned malformed payload' rather than proceeding with bad data.

Source

Thrown at clis/twitter/download.js:450

          let src = img.src || '';
          src = src.replace(/&name=\\w+$/, '&name=large');
          if (!src.includes('&name=')) src = src + '&name=large';
          out.push({ type: 'image', url: src });
        });
        document.querySelectorAll('video').forEach(video => {
          const src = video.src || '';
          if (src) out.push({ type: 'video', url: src });
        });
        document.querySelectorAll('[data-testid="videoPlayer"]').forEach(player => {
          const tweetLink = player.closest('article')?.querySelector('a[href*="/status/"]');
          const href = tweetLink?.getAttribute('href') || '';
          if (href) out.push({ type: 'video-tweet', url: 'https://x.com' + href });
        });
        return out;
      })()
    `));
    if (!Array.isArray(items)) {
        throw new CommandExecutionError('Twitter tweet media extraction returned malformed payload');
    }
    if (items.length === 0) {
        throw new EmptyResultError(`twitter download ${target.id}`, 'No media found in the tweet');
    }
    const cookies = await page.getCookies({ domain: 'x.com' });
    const seen = new Set();
    const unique = items.filter((m) => {
        if (seen.has(m.url)) return false;
        seen.add(m.url);
        return true;
    }).map((m) => {
        return { ...m, tweet_id: target.id };
    });
    return downloadTwitterMedia(unique, {
        output,
        subdir: 'tweets',
        cookies: formatCookieHeader(cookies),
        browserCookies: cookies,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the tweet URL loads in a logged-in browser and still contains media
  2. Retry the command in case of a transient page-load failure
  3. Update the CLI's extraction script if x.com changed its DOM structure
  4. Check the tweet URL is well-formed (parseTweetUrl succeeded but the tweet 404'd)
Defensive patterns

Strategy: type-guard

Validate before calling

const items = await extractMedia(page);
if (!Array.isArray(items) || items.length === 0) {
  throw new Error('Tweet page did not yield media; verify the URL and login state');
}

Type guard

const isMediaItems = (v) => Array.isArray(v) && v.every((i) => i && typeof i.url === 'string' && typeof i.type === 'string');

Try / catch

try {
  await cmd();
} catch (err) {
  if (err.message.includes('malformed payload')) {
    // re-check the tweet URL in a browser, then retry once
  }
}

Prevention

When it happens

Trigger: page.evaluate failing or returning a non-array: the browser script threw and the wrapper captured an error object, the page didn't load a tweet (login wall, deleted tweet, rate-limit interstitial), or unwrapBrowserResult returned null for a failed evaluation.

Common situations: Tweet deleted or account protected so the media DOM never renders; x.com serving a login-required page so selectors match nothing but evaluation errors out; browser navigation timed out before the 3-second wait completed; x.com markup changes breaking the script.

Understand the failure class

Related errors


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