{"id":"d122257b12b05a5c","repo":"rust-lang/cargo","slug":"failed-to-parse-process-output","errorCode":null,"errorMessage":"failed to parse process output: {}","messagePattern":"failed to parse process output: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cargo-util/src/process_builder.rs","lineNumber":440,"sourceCode":"            }\n            status\n        })()\n        .with_context(|| ProcessError::could_not_execute(self))?;\n        let output = Output {\n            status,\n            stdout,\n            stderr,\n        };\n\n        {\n            let to_print = if capture_output { Some(&output) } else { None };\n            if let Some(e) = callback_error {\n                let cx = ProcessError::new(\n                    &format!(\"failed to parse process output: {}\", self),\n                    Some(output.status),\n                    to_print,\n                );\n                bail!(anyhow::Error::new(cx).context(e));\n            } else if !output.status.success() {\n                bail!(ProcessError::new(\n                    &format!(\"process didn't exit successfully: {}\", self),\n                    Some(output.status),\n                    to_print,\n                ));\n            }\n        }\n\n        Ok(output)\n    }\n\n    /// Builds the command with an `@<path>` argfile that contains all the\n    /// arguments. This is primarily served for rustc/rustdoc command family.\n    fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> {\n        use std::io::Write as _;\n\n        let mut tmp = tempfile::Builder::new()","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/rust-lang/cargo/blob/0e07a155371a6ce88ae53a2c00df940280c09a67/crates/cargo-util/src/process_builder.rs#L422-L458","documentation":"From ProcessBuilder::exec_with_streaming (crates/cargo-util/src/process_builder.rs:346-451). For each stdout/stderr line it calls the on_stdout_line / on_stderr_line callback; the first Err is stashed in callback_error. After the child exits, if callback_error is set it bails wrapping the original error with a ProcessError whose message is `failed to parse process output: {self}`. It means the process ran but its line-by-line output could not be interpreted by the supplied callback.","triggerScenarios":"Cargo spawns rustc/rustdoc with --error-format=json (or a message-format=json callback) and a callback that serde-parses each line fails on a malformed line. A build script or wrapper emitting non-JSON into a JSON-expecting stream. Output truncated mid-line by a proxy.","commonSituations":"Version skew: a newer rustc emits JSON fields the older Cargo callback rejects. sccache/ccache/distcc or an IDE proxy interleaving or corrupting stdout. A build script printing stray text to stdout that Cargo tries to parse as build directives.","solutions":["Use matching rustc and Cargo versions from the same toolchain (rustup manages this).","Remove output-mangling wrappers (sccache/ccache/proxies) from the failing command to isolate the cause.","Re-run; transient truncation (OOM kill, pipe close) can produce malformed final lines.","If you supply the callback yourself, make it tolerant: skip/log unparseable lines instead of returning Err."],"exampleFix":"// before: callback hard-fails on any non-json line\nlet out = p.exec_with_streaming(\n    &mut |l| { serde_json::from_str::<Msg>(l)?; Ok(()) },\n    &mut |_| Ok(()),\n    true,\n)?;\n\n// after: skip lines that aren't valid json\nlet out = p.exec_with_streaming(\n    &mut |l| {\n        if serde_json::from_str::<Msg>(l).is_err() {\n            log::warn!(\"ignoring non-json line: {l}\");\n        }\n        Ok(())\n    },\n    &mut |_| Ok(()),\n    true,\n)?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Distinguish callback-parse failures from process-exit failures\nuse cargo_util::{ProcessError, ProcessBuilder};\n\nmatch p.exec_with_streaming(&mut cb_out, &mut cb_err, true) {\n    Ok(out) => out,\n    Err(e) => {\n        let is_parse = e.downcast_ref::<ProcessError>()\n            .map(|pe| pe.to_string().contains(\"failed to parse process output\"))\n            .unwrap_or(false);\n        if is_parse {\n            log::warn!(\"tool output parse failed; falling back to non-streaming\");\n            p.exec()? // retry without streaming callbacks\n        } else {\n            return Err(e);\n        }\n    }\n}","preventionTips":["Keep rustc and Cargo from the same toolchain to avoid JSON schema skew.","Make streaming callbacks tolerant: log/skip unparseable lines instead of returning Err.","Avoid wrapping build commands in proxies that interleave stdout."],"tags":["process","io","parsing","build","cargo-util"],"analyzedSha":"0e07a155371a6ce88ae53a2c00df940280c09a67","analyzedAt":"2026-08-06T01:46:58.334Z","schemaVersion":2}