jackwener/OpenCLI · error · CommandExecutionError
twitter_likes_protocol_error
twitter_likes_protocol_error
Error message
twitter_likes_protocol_error: missing Likes timeline instructions
What it means
CommandExecutionError (code twitter_likes_protocol_error) thrown when the Likes API responds OK but the payload contains no timeline instructions and does not look like a private-timeline response. This means Twitter returned an unrecognized payload shape, so parsing cannot proceed; the library treats it as a scraping-protocol break rather than an empty result.
Source
Thrown at clis/twitter/likes.js:318
const fetchCount = Math.min(100, remaining);
const apiUrl = buildLikesUrl(likesQueryId, userId, fetchCount, cursor);
const data = unwrapBrowserResult(await page.evaluate(`async () => {
const r = await fetch(${JSON.stringify(apiUrl)}, { headers: ${headers}, credentials: 'include' });
return r.ok ? await r.json() : { error: r.status };
}`));
if (data?.error) {
if ((useOutputFile ? outputCount : allTweets.length) === 0)
throw new CommandExecutionError(describeTwitterApiError('Likes', data.error));
break;
}
lastRawResponse = data;
const hasInstructions = Array.isArray(data?.data?.user?.result?.timeline_v2?.timeline?.instructions)
|| Array.isArray(data?.data?.user?.result?.timeline?.timeline?.instructions);
if (!hasInstructions) {
if (looksLikePrivateTwitterTimeline(data) && (useOutputFile ? outputCount : allTweets.length) === 0) {
throw new EmptyResultError('twitter likes', `No likes returned for @${username} (Likes are private by default on X; only the account owner can view their own likes)`);
}
throw new CommandExecutionError('twitter_likes_protocol_error: missing Likes timeline instructions');
}
const { tweets, nextCursor } = parseLikes(data, seen);
if (useOutputFile) {
appendJsonlRows(outputFile, tweets);
outputCount += tweets.length;
}
else {
allTweets.push(...tweets);
}
const pageComplete = !nextCursor;
writeResumeFile(resumeFile, {
cursor: pageComplete ? null : nextCursor,
count: useOutputFile ? outputCount : allTweets.length,
tweets: useOutputFile ? undefined : allTweets,
updatedAt: new Date().toISOString(),
complete: pageComplete,
source: 'likes',
username,View on GitHub (pinned to 49907e53dc)
Solutions
- Log the raw response (lastRawResponse) and inspect the actual JSON shape.
- Re-resolve the Likes queryId with resolveTwitterQueryId to ensure you hit the current GraphQL operation.
- Update parseLikes and the instructions paths in likes.js to match Twitter's new schema.
- Re-authenticate; a degraded session can yield non-timeline payloads.
Example fix
// before const instructions = data?.data?.user?.result?.timeline_v2?.timeline?.instructions; // after const instructions = data?.data?.user?.result?.timeline_v2?.timeline?.instructions || data?.data?.user?.result?.timeline?.timeline?.instructions || data?.data?.user?.result?.timeline?.instructions; // accommodate new/legacy shapes
Defensive patterns
Strategy: try-catch
Validate before calling
// after a probe request, verify the response shape before the full run
const probe = await fetchLikesPage(cursor = null);
const ok = Array.isArray(probe?.data?.user?.result?.timeline_v2?.timeline?.instructions)
|| Array.isArray(probe?.data?.user?.result?.timeline?.timeline?.instructions);
if (!ok) console.warn('Unexpected Likes payload shape; update parser/queryId.'); Type guard
function hasTimelineInstructions(data) {
const r = data?.data?.user?.result;
return Array.isArray(r?.timeline_v2?.timeline?.instructions)
|| Array.isArray(r?.timeline?.timeline?.instructions);
} Try / catch
try {
await cli.twitter.likes({ username });
} catch (e) {
if (e.code === 'twitter_likes_protocol_error') {
console.error('Twitter response schema changed; dump lastRawResponse and update parseLikes/query IDs.');
} else throw e;
} Prevention
- Always resolve query IDs dynamically instead of hardcoding
- Log lastRawResponse on protocol errors to diagnose schema changes
- Keep parser paths tolerant of both timeline_v2 and legacy shapes
- Re-authenticate: degraded sessions can return non-timeline payloads
When it happens
Trigger: After the API call at clis/twitter/likes.js:318, Array.isArray checks on data?.data?.user?.result?.timeline_v2?.timeline?.instructions and the legacy timeline path both fail, and looksLikePrivateTwitterTimeline is false (or some rows were already collected).
Common situations: Twitter changed the Likes GraphQL response schema; a stale queryId serving a different response shape; the account returned a guest/limited payload; soft-blocked requests returning ok-with-empty-body responses.
Related errors
- Barchart greeks returned an unreadable options payload${data
- twitter_bookmarks_protocol_error
- twitter_collection_protocol_error
- Upwork search state had an unexpected jobs shape; expected w
- Not a git repository
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/1ef2f26414f0ef8a.
Report an issue: GitHub.