{"record":{"id":"1bb6021d2052b632","repo":"NickvisionApps/Parabolic","slug":"unexpected-output-format-from-yt-dlp-processresult-output","errorCode":null,"errorMessage":"Unexpected output format from yt-dlp: {processResult.Output}","messagePattern":"Unexpected output format from yt-dlp: (.+?)","errorType":"exception","errorClass":"YtdlpException","httpStatus":null,"severity":"error","filePath":"Nickvision.Parabolic.Shared/Services/DiscoveryService.cs","lineNumber":78,"sourceCode":"    }\n\n    public Task<DiscoveryResult> GetForUrlAsync(Uri url, Credential? credential = null, CancellationToken cancellationToken = default) => GetForUrlAsync(url, credential, string.Empty, string.Empty, cancellationToken);\n\n    private async Task<DiscoveryResult> GetForUrlAsync(Uri url, Credential? credential, string suggestedSaveFolder, string suggestedFilename, CancellationToken cancellationToken = default)\n    {\n        _logger.LogInformation($\"Discovering media for {url}...\");\n        var processResult = await _ytdlpExecutableService.ExecuteAsync(_ytdlpExecutableService.GetDiscoveryProcessArguments(url, credential), cancellationToken);\n        if (processResult.ExitCode != 0 && (string.IsNullOrEmpty(processResult.Output) || processResult.Output[0] != '{'))\n        {\n            _logger.LogError($\"Failed to discover media for {url}: {processResult.Error.TrimEnd()}\");\n            throw new YtdlpException(processResult.Error);\n        }\n        cancellationToken.ThrowIfCancellationRequested();\n        using var json = JsonDocument.Parse(processResult.Output);\n        if (json.RootElement.ValueKind != JsonValueKind.Object)\n        {\n            _logger.LogError($\"Unexpected output format from yt-dlp for {url}: {processResult.Output.TrimEnd()}\");\n            throw new YtdlpException($\"Unexpected output format from yt-dlp: {processResult.Output}\");\n        }\n        DiscoveryResult? result = null;\n        if (json!.RootElement.TryGetProperty(\"entries\", out var entriesProperty) && entriesProperty.GetArrayLength() > 0)\n        {\n            var urlInfos = new List<DiscoveryResult>();\n            foreach (var entry in entriesProperty.EnumerateArray())\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n                if (entry.ValueKind != JsonValueKind.Object)\n                {\n                    continue;\n                }\n                if (entry.TryGetProperty(\"ie_key\", out var ieProperty) && (ieProperty.GetString() ?? string.Empty) == \"YoutubeTab\" && entry.TryGetProperty(\"url\", out var urlProperty))\n                {\n                    var urlInfo = await GetForUrlAsync(new Uri(urlProperty.GetString() ?? string.Empty), credential, suggestedSaveFolder, suggestedFilename, cancellationToken);\n                    if (urlInfo is not null)\n                    {\n                        urlInfos.Add(urlInfo);","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/NickvisionApps/Parabolic/blob/1118e6a3abbf5ffc1f764c4c9781f98d4c0482d4/Nickvision.Parabolic.Shared/Services/DiscoveryService.cs#L60-L96","documentation":"GetForUrlAsync runs yt-dlp expecting JSON on stdout, then throws YtdlpException when the output cannot be parsed as a JSON object (or is not an object at the root). This indicates yt-dlp emitted something unexpected instead of the anticipated JSON metadata document.","triggerScenarios":"Calling GetForUrlAsync(url) when yt-dlp writes warnings, progress text, an error message, or malformed data to stdout so JsonDocument.Parse fails or the root element is not a JSON object. Also occurs with an outdated/broken yt-dlp build or a wrapper script that prints extra output.","commonSituations":"Outdated yt-dlp version broken by a site change printing error text instead of JSON; antivirus/shell wrappers injecting output; URL requiring authentication so yt-dlp returns an error message; stderr redirected into stdout; disk-full or locale issues corrupting the output.","solutions":["Update yt-dlp to the latest version (yt-dlp -U or package manager) so it emits valid JSON for the target URL.","Run the exact yt-dlp dump-json command manually for the URL and inspect the raw stdout/stderr to see what is actually being printed.","Check that yt-dlp is writing JSON to stdout and all diagnostics to stderr; fix any redirection or wrapper that mixes them.","Verify the URL is reachable/supported (test without credentials, check for age/login restrictions).","Catch YtdlpException, log the raw output, and retry with a fresh yt-dlp invocation or a corrected URL."],"exampleFix":"// before\nvar result = await discoveryService.GetForUrlAsync(url); // yt-dlp outdated, prints error text\n// after\nRunYtdlpUpdateCheck(); // ensure yt-dlp is current before invoking\ntry\n{\n    var result = await discoveryService.GetForUrlAsync(url);\n}\ncatch (YtdlpException ex)\n{\n    _logger.LogError(ex, \"yt-dlp returned non-JSON output for {Url}; run yt-dlp -U and verify the URL\", url);\n}","handlingStrategy":"try-catch","validationCode":"static bool LooksLikeJson(string s)\n{\n    s = s.TrimStart();\n    return s.StartsWith(\"{\") || s.StartsWith(\"[\");\n}\n// check processResult.Output with LooksLikeJson before/after calling GetForUrlAsync","typeGuard":"static bool IsJsonObjectOutput(string output)\n{\n    try { using var doc = JsonDocument.Parse(output); return doc.RootElement.ValueKind == JsonValueKind.Object; }\n    catch (JsonException) { return false; }\n}","tryCatchPattern":"try { var result = await service.GetForUrlAsync(url); }\ncatch (YtdlpException ex) { _logger.LogError(ex, \"yt-dlp output was not JSON for {Url}; update yt-dlp and check URL support\", url); }","preventionTips":["Keep yt-dlp updated (yt-dlp -U) before discovery runs.","Probe unknown URLs by running yt-dlp's JSON dump manually to confirm the site is supported.","Ensure yt-dlp diagnostics go to stderr and only JSON to stdout; don't merge streams.","Retry once on YtdlpException after confirming the binary version and URL validity."],"tags":["yt-dlp","json","external-process"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"1118e6a3abbf5ffc1f764c4c9781f98d4c0482d4","analyzedAt":"2026-09-15T05:27:17.985Z","contentChangedAt":"2026-09-15T05:27:17.985Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}