{"record":{"id":"db4a967bb960bee3","repo":"tonhowtf/omniget","slug":"yt-dlp-playlist-failed","errorCode":null,"errorMessage":"yt-dlp playlist failed: {}","messagePattern":"yt-dlp playlist failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/ytdlp.rs","lineNumber":2606,"sourceCode":"\n    if !output.status.success() {\n        let stderr = String::from_utf8_lossy(&output.stderr);\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            let player_client = if is_youtube_url(url) {\n                \"default\"\n            } else {\n                \"n/a\"\n            };\n            tracing::warn!(\n                \"[yt-429] rate limit in get_playlist_info: url={} player_client={} retries=3\",\n                sanitized_url,\n                player_client\n            );\n        }\n        return Err(anyhow!(\n            \"yt-dlp playlist failed: {}\",\n            extract_error_message(&stderr)\n        ));\n    }\n\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    Ok(parse_playlist_dump(&stdout, url))\n}\n\nfn parse_playlist_dump(stdout: &str, source_url: &str) -> (String, Vec<PlaylistEntry>) {\n    let mut entries = Vec::new();\n    let mut playlist_title = String::new();\n    let source_is_youtube = is_youtube_url(source_url);\n\n    for line in stdout.lines() {\n        if line.trim().is_empty() {\n            continue;\n        }","sourceCodeStart":2588,"sourceCodeEnd":2624,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/ytdlp.rs#L2588-L2624","documentation":"When the playlist yt-dlp run exits non-zero, the code extracts a human-readable message from stderr with extract_error_message and returns it wrapped in this error. It means yt-dlp itself failed on the playlist URL — the wrapper is relaying yt-dlp's own diagnostic.","triggerScenarios":"yt-dlp exits with failure while listing a playlist: HTTP 429 rate limiting (tracked separately), unsupported/private/deleted playlist, geo-block, signature/extractor breakage from an outdated yt-dlp, or bad cookies.","commonSituations":"Outdated yt-dlp after a YouTube layout change; rate-limited IPs on shared hosts; private or members-only playlists without valid cookies; URLs that are not actually playlists.","solutions":["Update yt-dlp to the latest version — most extractor failures are fixed upstream","Check the embedded stderr message: handle 429 with backoff, auth errors by refreshing cookies","Verify the playlist URL is public and correctly formed","Retry with different player clients or add browser cookies via --cookies-from-browser"],"exampleFix":"// before\nreturn Err(anyhow!(\"yt-dlp playlist failed: {}\", extract_error_message(&stderr)));\n// after\nif stderr_lower.contains(\"http error 429\") {\n    rate_limit_429_increment();\n    return Err(anyhow!(\"yt-dlp playlist failed (rate limited, retry later): {}\", extract_error_message(&stderr)));\n}\nreturn Err(anyhow!(\"yt-dlp playlist failed: {}\", extract_error_message(&stderr)));","handlingStrategy":"fallback","validationCode":"// Preflight: confirm yt-dlp can reach the site with a cheap single-item probe\nlet probe = ytdlp_command(ytdlp)\n    .args([\"--flat-playlist\", \"--playlist-items\", \"1\", \"--dump-json\", url])\n    .output()\n    .await;\nif !probe.map(|o| o.status.success()).unwrap_or(false) {\n    return Err(\"playlist URL rejected by yt-dlp preflight; check URL/auth/yt-dlp version\".into());\n}","typeGuard":null,"tryCatchPattern":"match get_playlist_info(url).await {\n    Err(e) if e.to_string().contains(\"http error 429\") => wait_and_retry_with_backoff(url),\n    Err(e) if e.to_string().contains(\"yt-dlp playlist failed\") => try_fallback_extractor_or_notify(e),\n    other => other,\n}","preventionTips":["Keep yt-dlp updated; most non-zero exits are extractor breakage fixed upstream","Honor the app's 429 rate-limit tracking with real backoff before retries","Require cookies/credentials for private playlists and refresh them proactively","Validate playlist URLs before submission"],"tags":["rust","ytdlp","playlist","stderr"],"backgroundTag":"api-error-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"}