tonhowtf/omniget · error

X : HTTP

Error message

X {}: HTTP {} {}

What it means

Catch-all for non-success X API responses that are not 404 (treated as not_found) or 401/403 (treated as auth errors): the client errors with 'X <op>: HTTP <status> <first-200-chars-of-body>', giving the failing operation name plus a truncated body for diagnosis.

Solutions

  1. Read the truncated body in the message — it usually names the exact problem (bad request field, blocked op).
  2. Retry on 5xx with backoff; treat 400 as a bug in the query variables you pass.
  3. Verify the GraphQL operation/queryId is current — X rotates these and stale ones fail.
  4. Check X status/incident reports if 5xx persists across operations.

Example fix

// before
client.gql_get("TweetDetail", &vars).await?; // fails: X TweetDetail: HTTP 400 {"errors":...}
// after
let v = client.gql_get("TweetDetail", &vars).await
    .map_err(|e| { tracing::error!(op = "TweetDetail", err = %e, "x gql failed"); e })?;
Defensive patterns

Strategy: try-catch

Try / catch

match client.gql_get(op, &q).await {
    Err(e) if e.to_string().starts_with("X ") && e.to_string().contains("HTTP 5") => {
        backoff_retry(op, max_attempts = 3)
    }
    Err(e) => Err(e),
    ok => ok,
}

Prevention

When it happens

Trigger: check() receives statuses like 400, 404-as-unexpected, 5xx, or 429-adjacent errors outside the special-cased codes while executing a GraphQL op named in the message.

Common situations: X server errors (5xx) or maintenance; malformed query variables causing 400 Bad Request; deprecated GraphQL endpoints returning unexpected statuses after X API changes.

Related errors


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

Appendix: source

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

        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))?;
        if v.get("data")
            .map(|d| d.is_null() || d.as_object().map(|o| o.is_empty()).unwrap_or(false))
            .unwrap_or(true)
        {
            if let Some(msg) = v
                .get("errors")
                .and_then(|e| e.as_array())
                .and_then(|a| a.first())
                .and_then(|e| e.get("message"))
                .and_then(|m| m.as_str())

View on GitHub (pinned to 8600b91f42)