tonhowtf/omniget · error · anyhow::Error

Twitch GQL: {}

Error message

Twitch GQL: {}

What it means

The public anonymous query helper checks the GQL response for a GraphQL `errors` array (via first_error) and, if present, wraps the first error message in this error. HTTP succeeded, but Twitch rejected the operation at the GraphQL level — bad query syntax, unknown fields, or a data-layer error such as a nonexistent resource. It surfaces the server-side GraphQL error text directly to the caller.

Solutions

  1. Read the message text — it contains Twitch's own GraphQL error, usually naming the offending field or problem.
  2. Validate the query against the current Twitch GQL schema (introspection or a GQL playground) and fix removed/renamed fields.
  3. Check the entity being queried (channel login, video/clip id) actually exists and is public.
  4. If Twitch rotated its schema, update the query strings in channel/video/clip_video to the new shape.
  5. Consider adding fallback logic that inspects the full `errors` array instead of only the first message.

Example fix

// before
if let Some(msg) = first_error(&json) {
    bail!("Twitch GQL: {}", msg);
}
// after
if let Some(msg) = first_error(&json) {
    bail!("Twitch GQL: {} (query: {}...)", msg, &query[..query.len().min(80)]);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check the entity exists / query is non-empty before calling
if query.trim().is_empty() { anyhow::bail!("empty GQL query"); }

Try / catch

match gql.query(&q).await {
    Err(e) if e.to_string().starts_with("Twitch GQL:") => {
        // GraphQL-level failure: parse message, fix schema fields or entity id
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling GqlClient::query with a query string that Twitch accepts over HTTP but fails to execute — unknown field/operation, syntax error in the query, variables mismatch, or entity not found — causing a non-empty `errors` array in the JSON response.

Common situations: Twitch renamed/removed fields from its public GQL schema so channel/video/clip queries reference nonexistent fields; typos in hand-written GraphQL strings; querying private/deleted channels or videos; using a persisted operation shape with the anonymous query endpoint.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/twitch/gql.rs:177

            let status = resp.status();
            if status.as_u16() == 429 || status.is_server_error() {
                last = format!("HTTP {}", status);
                continue;
            }
            if !status.is_success() {
                bail!("Twitch GQL respondeu HTTP {}", status);
            }
            return Ok(resp.json::<Value>().await?);
        }
        bail!("Twitch GQL não respondeu depois de 5 tentativas: {}", last)
    }

    /// Query anônima em texto (as públicas aceitam sem persisted query).
    pub async fn query(&self, query: &str) -> anyhow::Result<Value> {
        let body = json!({ "query": query });
        let json = self.post(&body).await?;
        if let Some(msg) = first_error(&json) {
            bail!("Twitch GQL: {}", msg);
        }
        json.get("data")
            .cloned()
            .ok_or_else(|| anyhow!("resposta do Twitch GQL sem `data`"))
    }

    /// Persisted query (formato em lote, como o site manda). Devolve o
    /// primeiro elemento cru, para quem quiser ler `errors` também.
    pub async fn persisted(&self, op: &str, hash: &str, vars: Value) -> anyhow::Result<Value> {
        let body = json!([{
            "operationName": op,
            "variables": vars,
            "extensions": { "persistedQuery": { "version": 1, "sha256Hash": hash } },
        }]);
        let json = self.post(&body).await?;
        json.as_array()
            .and_then(|a| a.first())
            .cloned()

View on GitHub (pinned to 8600b91f42)