{"record":{"id":"cd56b15aac30dd41","repo":"tonhowtf/omniget","slug":"yt-dlp-returned-invalid-json","errorCode":null,"errorMessage":"yt-dlp returned invalid JSON: {}","messagePattern":"yt-dlp returned invalid JSON: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/ytdlp.rs","lineNumber":2351,"sourceCode":"            anyhow!(\n                \"Timeout fetching video info ({}s)\",\n                VIDEO_INFO_PROCESS_TIMEOUT_SECS\n            )\n        })?\n        .map_err(|e| {\n            tracing::debug!(\"[perf] get_video_info took {:?}\", _timer_start.elapsed());\n            anyhow!(\"Failed to run yt-dlp: {}\", e)\n        })?;\n\n        tracing::debug!(\n            \"[perf] get_video_info: yt-dlp process exited at {:?} (attempt {})\",\n            _timer_start.elapsed(),\n            attempt + 1\n        );\n\n        if result.status.success() {\n            let json: serde_json::Value = serde_json::from_slice(&result.stdout)\n                .map_err(|e| anyhow!(\"yt-dlp returned invalid JSON: {}\", e))?;\n            tracing::debug!(\"[perf] get_video_info took {:?}\", _timer_start.elapsed());\n            return Ok(json);\n        }\n\n        let stderr = String::from_utf8_lossy(&result.stderr).to_string();\n        tracing::debug!(\n            \"[yt-dlp info] stderr ({} bytes): {}\",\n            stderr.len(),\n            stderr.trim()\n        );\n        let stderr_lower = stderr.to_lowercase();\n        if stderr_lower.contains(\"http error 429\") {\n            rate_limit_429_increment();\n            let sanitized_url = sanitize_log_line(url);\n            tracing::warn!(\n                \"[yt-429] rate limit in get_video_info: url={} attempt={}/{}\",\n                sanitized_url,\n                attempt + 1,","sourceCodeStart":2333,"sourceCodeEnd":2369,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/ytdlp.rs#L2333-L2369","documentation":"When yt-dlp exits successfully (status.success()), get_video_info parses its stdout as serde_json::Value. If stdout is not valid JSON (or is empty/garbage), this error is returned with the serde error. yt-dlp normally emits JSON when run with --dump-json/-j, so this indicates the command args or yt-dlp output changed.","triggerScenarios":"yt-dlp exits 0 but stdout contains warnings, progress text, or HTML instead of pure JSON — e.g. a wrong --dump-json flag, an old/patched yt-dlp whose output format changed, output polluted by a config file, or stdout redirected/mixed with another source.","commonSituations":"A user's global yt-dlp.conf injecting non-JSON output; a wrapper script printing banners; version drift where yt-dlp no longer supports the flag the app passes; locale/encoding issues corrupting output.","solutions":["Pin/update yt-dlp to a known-good version and ensure args still request JSON (--dump-json / -j)","Check for a user yt-dlp config file injecting extra output and pass --ignore-config","Capture and log the first bytes of stdout on failure to diagnose what was actually emitted","Wrap parsing to include a stdout snippet in the error message for easier debugging"],"exampleFix":"// before\n.map_err(|e| anyhow!(\"yt-dlp returned invalid JSON: {}\", e))\n// after\n.map_err(|e| anyhow!(\"yt-dlp returned invalid JSON: {}; stdout head: {:?}\",\n    e, String::from_utf8_lossy(&result.stdout.chars().take(200).collect::<Vec<_>>())))","handlingStrategy":"validation","validationCode":"// After a successful exit, validate stdout looks like JSON before full parse\nlet out = String::from_utf8_lossy(&result.stdout);\nlet trimmed = out.trim_start();\nif !(trimmed.starts_with('{') || trimmed.starts_with('[')) {\n    return Err(format!(\"yt-dlp output is not JSON (head: {:.120})\", out));\n}","typeGuard":"fn looks_like_json(bytes: &[u8]) -> bool {\n    let s = String::from_utf8_lossy(bytes);\n    let t = s.trim_start();\n    t.starts_with('{') || t.starts_with('[')\n}","tryCatchPattern":"match get_video_info(url).await {\n    Err(e) if e.to_string().contains(\"invalid JSON\") => {\n        // check yt-dlp version, --ignore-config, and re-run with logged stdout\n    }\n    other => other,\n}","preventionTips":["Always pass explicit JSON flags (--dump-json/-j) rather than relying on defaults","Run yt-dlp with --ignore-config so user config files cannot pollute stdout","Pin and regularly update yt-dlp; test output shape after upgrades","Log a stdout head snippet whenever parsing fails"],"tags":["rust","ytdlp","json","parsing"],"backgroundTag":"invalid-json-response","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"}