tonhowtf/omniget · error

resposta em lote inesperada do Twitch GQL

Error message

resposta em lote inesperada do Twitch GQL

What it means

persisted() sends the batched persisted-query format (a JSON array) and expects an array response whose first element is the page payload. It throws when the response is not an array (or is empty), i.e. the batch shape Twitch normally returns came back in an unexpected form.

Solutions

  1. Update the persisted query hash / client version to match the current Twitch site
  2. Retry after a short delay — can be transient
  3. Inspect the raw response to confirm whether it's an error object or HTML interstitial
  4. Fall back to the regular (non-persisted) query path if the library offers one

Example fix

// before
let raw = gql.persisted("Comments", OLD_HASH, vars).await?;
// after
let raw = match gql.persisted("Comments", CURRENT_HASH, vars).await {
    Ok(r) => r,
    Err(e) if e.to_string().contains("lote inesperada") => {
        tracing::warn!("persisted query shape changed; check hash/headers");
        return Err(e);
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot validate remotely; keep the persisted query hash current with the Twitch site.
let hash_ok = PERSISTED_HASH.len() == 64
    && PERSISTED_HASH.chars().all(|c| c.is_ascii_hexdigit());

Type guard

fn is_batch_response(json: &serde_json::Value) -> bool {
    json.as_array().map_or(false, |a| !a.is_empty())
}

Try / catch

match client.persisted(op, hash, vars).await {
    Err(e) if e.to_string().contains("lote inesperada") => {
        tracing::warn!("batch shape changed — refresh hash/headers");
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: fetch_all (VOD chat via persisted query) when the endpoint responds with a single JSON object instead of a batch array, an empty array, or an error object — commonly when the persisted query hash is outdated or blocked.

Common situations: Twitch changing/retiring a persisted query hash (client out of date); bot protection returning a non-batch error; intermittent Twitch API incidents; hitting the endpoint without required Client-Integrity headers.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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

Appendix: source

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

        }
        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()
            .ok_or_else(|| anyhow!("resposta em lote inesperada do Twitch GQL"))
    }

    pub async fn channel(&self, login: &str) -> anyhow::Result<Channel> {
        let q = format!(
            r#"{{ user(login: "{}") {{ id login displayName profileImageURL(width: 300) }} }}"#,
            escape(login)
        );
        let data = self.query(&q).await?;
        let u = data.get("user").filter(|v| !v.is_null());
        let u = u.ok_or_else(|| anyhow!("canal não encontrado: {}", login))?;
        Ok(Channel {
            id: str_at(u, "id"),
            login: str_at(u, "login"),
            display_name: str_at(u, "displayName"),
            avatar: str_at(u, "profileImageURL"),
        })
    }

View on GitHub (pinned to 8600b91f42)