jackwener/OpenCLI · error · CommandExecutionError

device_follow

Error message

device_follow

What it means

For any other GraphQL error status (not 401/403, not a parse/exception failure), the command builds a human-readable message via describeTwitterApiError('device_follow', data.error) and throws CommandExecutionError. The raw message 'device_follow' is the API name passed into that describer, so the thrown text summarizes an API-level error for the device_follow endpoint.

Source

Thrown at clis/twitter/device-follow.js:169

            return await r.json();
          } catch (e) {
            return { errorKind: 'non_json', detail: String(e && e.message || e) };
          }
        } catch (e) {
          return { errorKind: 'exception', detail: String(e && e.message || e) };
        }
      }`);
        if (data?.errorKind === 'non_json') {
            throw new CommandExecutionError(`Twitter device-follow returned non-JSON response: ${data.detail || 'unknown parse error'}`);
        }
        if (data?.errorKind === 'exception') {
            throw new CommandExecutionError(`Twitter device-follow fetch failed: ${data.detail || 'unknown error'}`);
        }
        if (data?.error) {
            if (data.error === 401 || data.error === 403) {
                throw new AuthRequiredError('x.com', `Twitter device-follow returned HTTP ${data.error}`);
            }
            throw new CommandExecutionError(describeTwitterApiError('device_follow', data.error));
        }
        const parsed = parseDeviceFollow(data, new Set());
        if (!parsed) {
            throw new CommandExecutionError('Twitter device-follow response was missing the expected timeline/globalObjects shape.');
        }
        if (parsed.malformedEntries > 0 || parsed.unmatchedTweetEntries > 0) {
            throw new CommandExecutionError('Twitter device-follow entries could not be joined to tweet/user objects.');
        }
        if (parsed.rows.length === 0) {
            throw new EmptyResultError('twitter device-follow', 'No device-follow notification tweets found.');
        }
        const rows = parsed.rows;
        const trimmed = rows.slice(0, limit);
        return applyTopByEngagement(trimmed, kwargs['top-by-engagement']);
    },
});

export const __test__ = {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the full describeTwitterApiError message for the specific code
  2. Back off and retry with lower frequency if the code indicates rate limiting (429)
  3. Check whether the endpoint was changed/deprecated and update the CLI
  4. Retry later for 5xx codes — often a Twitter-side incident
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check not possible for API-side codes; rate-limit friendly pacing instead
const sleepMs = Number(process.env.TWITTER_MIN_INTERVAL_MS || 2000);

Try / catch

try {
  await cli.twitter.deviceFollow();
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('429')) {
    await sleep(15 * 60_000); // rate limited: long backoff
    return retryOnce();
  }
  if (e instanceof CommandExecutionError) throw e; // other API codes: inspect message
  throw e;
}

Prevention

When it happens

Trigger: The device_follow GraphQL endpoint returns a JSON body with an error field whose code/status is anything other than 401 or 403 (e.g. 404, 429 rate limit, 5xx, or a GraphQL errors array code).

Common situations: Hitting rate limits (429) during heavy scraping; Twitter deprecating or relocating the device_follow endpoint (404); transient 5xx server errors; account restrictions returning unusual codes.

Related errors


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