tonhowtf/omniget · warning

X_RATE_LIMIT

X_RATE_LIMIT

Error message

X_RATE_LIMIT:{}

What it means

The X client's response check converts HTTP 429 into the sentinel error 'X_RATE_LIMIT:<seconds>' where <seconds> is derived from the x-rate-limit-reset header (fallback 900s). Callers are expected to parse the reset value and wait that long before retrying the GraphQL operation.

Solutions

  1. Parse the integer after 'X_RATE_LIMIT:' and schedule a retry after that many seconds.
  2. Add exponential backoff with jitter and cache results to reduce request volume.
  3. Spread requests across endpoints/accounts, and respect x-rate-limit-remaining headers proactively.
  4. Stop polling loops that ignore prior 429s — they extend the lockout.

Example fix

// before
loop { match client.gql_get(op, &q).await { Ok(v) => break v, Err(_) => continue } }
// after
match client.gql_get(op, &q).await {
    Ok(v) => v,
    Err(e) if e.to_string().starts_with("X_RATE_LIMIT:") => {
        let reset: i64 = e.to_string().rsplit(':').next().unwrap().parse()?;
        tokio::time::sleep(Duration::from_secs(reset as u64 + 1)).await;
        client.gql_get(op, &q).await?
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Try / catch

// parse reset seconds and sleep before retrying
if let Some(reset) = e.to_string().strip_prefix("X_RATE_LIMIT:").and_then(|s| s.parse::<u64>().ok()) {
    tokio::time::sleep(Duration::from_secs(reset + 1)).await;
    return op().await; // one bounded retry
}

Prevention

When it happens

Trigger: Any gql request whose response carries status 429 — exceeding X's per-endpoint rate limits, especially when polling rapidly or sharing one account/IP across many requests.

Common situations: Tight retry loops hammering one GraphQL endpoint; batch jobs without backoff; multiple app instances behind the same IP consuming the shared limit.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/0673fb986da6d321. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/x/client.rs:304

        let path = format!("/i/api/graphql/{}/{}", id, op);
        if self.authed() {
            (format!("https://x.com{}", path), path)
        } else {
            (format!("https://api.x.com/graphql/{}/{}", id, op), path)
        }
    }

    async fn check(resp: reqwest::Response, op: &str) -> anyhow::Result<Result<Value, String>> {
        let status = resp.status();
        if status.as_u16() == 429 {
            let reset = resp
                .headers()
                .get("x-rate-limit-reset")
                .and_then(|v| v.to_str().ok())
                .and_then(|v| v.parse::<i64>().ok())
                .map(|r| (r - chrono::Utc::now().timestamp()).max(1))
                .unwrap_or(900);
            return Err(anyhow!("X_RATE_LIMIT:{}", reset));
        }
        let text = resp.text().await.unwrap_or_default();
        if status.as_u16() == 404 {
            return Ok(Err("not_found".into()));
        }
        if status.as_u16() == 401 || status.as_u16() == 403 {
            return Ok(Err(format!("auth:{}", status.as_u16())));
        }
        if !status.is_success() {
            return Err(anyhow!(
                "X {}: HTTP {} {}",
                op,
                status,
                text.chars().take(200).collect::<String>()
            ));
        }
        let v: Value = serde_json::from_str(&text)
            .map_err(|e| anyhow!("X {}: resposta invalida ({})", op, e))?;

View on GitHub (pinned to 8600b91f42)