jackwener/OpenCLI · error · CommandExecutionError

twitter_collection_protocol_error

twitter_collection_protocol_error

Error message

twitter_collection_protocol_error: timeline post is missing a stable ID

What it means

extractCollectionPost unwraps each timeline entry's tweet result and requires a non-empty string rest_id; entries without a stable ID cannot be deduplicated or referenced downstream, so the library throws a protocol error indicating the payload violates its expected shape.

Source

Thrown at clis/twitter/collection.js:161

    if (replyId || replyHandle || replyAuthorId) {
        return {
            kind: 'reply',
            target: {
                post_id: replyId,
                author_handle: replyHandle,
                author_id: replyAuthorId,
                url: replyId && replyHandle ? `https://x.com/${replyHandle}/status/${replyId}` : null,
                context_status: replyId ? 'unavailable' : 'unknown',
            },
        };
    }
    return { kind: 'original', target: null };
}

function extractCollectionPost(result, seen) {
    const tweet = unwrapTweetResult(result);
    if (!tweet?.rest_id || typeof tweet.rest_id !== 'string') {
        throw new CommandExecutionError('twitter_collection_protocol_error: timeline post is missing a stable ID');
    }
    if (seen.has(tweet.rest_id)) return null;
    seen.add(tweet.rest_id);
    const legacy = tweet.legacy || {};
    const user = tweet.core?.user_results?.result;
    const author = user?.legacy?.screen_name || user?.core?.screen_name || null;
    if (!author || !normalizeTwitterScreenName(author)) {
        throw new CommandExecutionError('twitter_collection_protocol_error: timeline post is missing an author handle');
    }
    return {
        id: tweet.rest_id,
        author,
        name: user?.legacy?.name || user?.core?.name || '',
        text: tweet.note_tweet?.note_tweet_results?.result?.text || legacy.full_text || '',
        likes: legacy.favorite_count || 0,
        retweets: legacy.retweet_count || 0,
        replies: legacy.reply_count || 0,
        views: Number(tweet.views?.count) || 0,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Update the CLI to match the current X GraphQL response shape
  2. Inspect the offending entry's JSON to see where the ID now lives and report/patch unwrapTweetResult
  3. Filter out tombstone/promoted entries upstream if your tooling wraps this library
  4. Re-run the collection to see if the malformed entry was transient

Example fix

null
Defensive patterns

Strategy: type-guard

Validate before calling

function entryHasStableId(entry) {
  const tweet = unwrapTweetResult(entry);
  return tweet != null && typeof tweet.rest_id === 'string' && tweet.rest_id.length > 0;
}
const safeEntries = entries.filter(entryHasStableId);

Type guard

function hasRestId(t) {
  return t != null && typeof t === 'object' && typeof t.rest_id === 'string' && t.rest_id.length > 0;
}

Try / catch

try {
  const post = extractCollectionPost(entry, seen);
} catch (err) {
  if (String(err.message).startsWith('twitter_collection_protocol_error')) {
    console.warn('skipping malformed timeline entry');
  } else throw err;
}

Prevention

When it happens

Trigger: A timeline/collection entry contains an unwrapped tweet result lacking rest_id (or non-string rest_id) — e.g. tombstone entries, ad/promoted entries, or X GraphQL shape changes where the ID moved elsewhere in the object.

Common situations: New X API response versions with changed module wrappers; sponsored/tombstoned timeline modules in user timelines; parsing older cached payloads with a newer CLI expecting rest_id.

Related errors


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