{"record":{"id":"7c836309d5253cff","repo":"tonhowtf/omniget","slug":"resposta-em-lote-inesperada-do-twitch-gql","errorCode":null,"errorMessage":"resposta em lote inesperada do Twitch GQL","messagePattern":"resposta em lote inesperada do Twitch GQL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/tools/twitch/gql.rs","lineNumber":196,"sourceCode":"        }\n        json.get(\"data\")\n            .cloned()\n            .ok_or_else(|| anyhow!(\"resposta do Twitch GQL sem `data`\"))\n    }\n\n    /// Persisted query (formato em lote, como o site manda). Devolve o\n    /// primeiro elemento cru, para quem quiser ler `errors` também.\n    pub async fn persisted(&self, op: &str, hash: &str, vars: Value) -> anyhow::Result<Value> {\n        let body = json!([{\n            \"operationName\": op,\n            \"variables\": vars,\n            \"extensions\": { \"persistedQuery\": { \"version\": 1, \"sha256Hash\": hash } },\n        }]);\n        let json = self.post(&body).await?;\n        json.as_array()\n            .and_then(|a| a.first())\n            .cloned()\n            .ok_or_else(|| anyhow!(\"resposta em lote inesperada do Twitch GQL\"))\n    }\n\n    pub async fn channel(&self, login: &str) -> anyhow::Result<Channel> {\n        let q = format!(\n            r#\"{{ user(login: \"{}\") {{ id login displayName profileImageURL(width: 300) }} }}\"#,\n            escape(login)\n        );\n        let data = self.query(&q).await?;\n        let u = data.get(\"user\").filter(|v| !v.is_null());\n        let u = u.ok_or_else(|| anyhow!(\"canal não encontrado: {}\", login))?;\n        Ok(Channel {\n            id: str_at(u, \"id\"),\n            login: str_at(u, \"login\"),\n            display_name: str_at(u, \"displayName\"),\n            avatar: str_at(u, \"profileImageURL\"),\n        })\n    }\n","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/tools/twitch/gql.rs#L178-L214","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Update the persisted query hash / client version to match the current Twitch site","Retry after a short delay — can be transient","Inspect the raw response to confirm whether it's an error object or HTML interstitial","Fall back to the regular (non-persisted) query path if the library offers one"],"exampleFix":"// before\nlet raw = gql.persisted(\"Comments\", OLD_HASH, vars).await?;\n// after\nlet raw = match gql.persisted(\"Comments\", CURRENT_HASH, vars).await {\n    Ok(r) => r,\n    Err(e) if e.to_string().contains(\"lote inesperada\") => {\n        tracing::warn!(\"persisted query shape changed; check hash/headers\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":"// Cannot validate remotely; keep the persisted query hash current with the Twitch site.\nlet hash_ok = PERSISTED_HASH.len() == 64\n    && PERSISTED_HASH.chars().all(|c| c.is_ascii_hexdigit());","typeGuard":"fn is_batch_response(json: &serde_json::Value) -> bool {\n    json.as_array().map_or(false, |a| !a.is_empty())\n}","tryCatchPattern":"match client.persisted(op, hash, vars).await {\n    Err(e) if e.to_string().contains(\"lote inesperada\") => {\n        tracing::warn!(\"batch shape changed — refresh hash/headers\");\n        Err(e)\n    }\n    other => other,\n}","preventionTips":["Keep persisted query hashes synced with the current Twitch web client","Include the headers (Client-Id, etc.) the site sends","Retry briefly — batch-shape errors can be intermittent","Fall back to the non-persisted query path where available"],"tags":["twitch","gql","persisted-query","batch","malformed-response"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}