{"record":{"id":"ae127109a2967001","repo":"tonhowtf/omniget","slug":"clip-not-found","errorCode":null,"errorMessage":"Clip not found: {}","messagePattern":"Clip not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/platforms/twitch.rs","lineNumber":146,"sourceCode":"        let body = serde_json::json!({ \"query\": query });\n\n        let response = self\n            .client\n            .post(GQL_URL)\n            .header(\"client-id\", CLIENT_ID)\n            .json(&body)\n            .send()\n            .await?;\n\n        if !response.status().is_success() {\n            return Err(anyhow!(\"Twitch GQL retornou HTTP {}\", response.status()));\n        }\n\n        let json: serde_json::Value = response.json().await?;\n\n        let clip = json\n            .pointer(\"/data/clip\")\n            .ok_or_else(|| anyhow!(\"Clip not found: {}\", slug))?;\n\n        if clip.is_null() {\n            return Err(anyhow!(\"Clip not found: {}\", slug));\n        }\n\n        let title = clip\n            .get(\"title\")\n            .and_then(|v| v.as_str())\n            .unwrap_or(\"Untitled\")\n            .to_string();\n\n        let duration_seconds = clip\n            .get(\"durationSeconds\")\n            .and_then(|v| v.as_f64())\n            .unwrap_or(0.0);\n\n        let thumbnail_url = clip\n            .get(\"medium\")","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/twitch.rs#L128-L164","documentation":"Raised by fetch_clip_metadata when the successful GQL response JSON has no /data/clip key at all (json.pointer returns None). This indicates the response structure itself lacks the clip object — usually a GraphQL-level error response (errors array, data null) rather than a normal result.","triggerScenarios":"GQL returns HTTP 200 but body is {\"data\":null,\"errors\":[...]} or an entirely different envelope (e.g. persisted-query rejection), so /data/clip does not exist.","commonSituations":"Twitch persisted-query or schema change invalidating the inline query; GQL returning a structured errors array for an unknown slug; proxies returning HTML error pages that fail earlier at json parse.","solutions":["Inspect the response body for an errors array and surface the GQL error message","Fall back to yt-dlp extraction for this URL","Update the GraphQL query to match the current Twitch schema"],"exampleFix":"// before\nlet clip = json.pointer(\"/data/clip\").ok_or_else(|| anyhow!(\"Clip not found: {}\", slug))?;\n// after\nlet clip = json.pointer(\"/data/clip\").ok_or_else(|| {\n    let gql_err = json.pointer(\"/errors/0/message\").and_then(|v| v.as_str()).unwrap_or(\"no data.clip in response\");\n    anyhow!(\"Clip lookup failed for {}: {}\", slug, gql_err)\n})?;","handlingStrategy":"try-catch","validationCode":"// Detect a GQL error envelope before treating the response as a clip result:\nlet is_gql_error = body.contains(\"\\\"errors\\\"\") && body.contains(\"\\\"data\\\":null\");","typeGuard":null,"tryCatchPattern":"match downloader.get_media_info(url).await {\n    Err(e) if e.to_string().contains(\"Clip not found\") && !e.to_string().contains(\"is null\") => {\n        log::warn!(\"GQL envelope lacked data.clip — likely schema/persisted-query change\");\n        fallback_ytdlp_info(url).await\n    }\n    other => other,\n}","preventionTips":["Log the raw GQL body when data.clip is absent to distinguish errors from genuine miss","Keep the GraphQL query aligned with the live Twitch schema","Route envelope-shaped failures to yt-dlp instead of failing outright"],"tags":["rust","twitch","graphql","response-shape"],"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"}