{"record":{"id":"6eb0a846819ae981","repo":"warpdotdev/warp","slug":"failed-to-parse-grep-output-unexpected-format","errorCode":null,"errorMessage":"Failed to parse Grep output, unexpected format","messagePattern":"Failed to parse Grep output, unexpected format","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/ai/blocklist/action_model/execute/grep.rs","lineNumber":656,"sourceCode":"/// Parses the output of grep or a grep-like command into the format that we pass\n/// back to the agent.\n///\n/// Assumes the output is in the format:\n/// `{relative_file_path}:{line_number}:{line_contents}`.\nfn parse_grep_output(\n    output: &str,\n    shell_launch_data: Option<ShellLaunchData>,\n    current_working_directory: Option<String>,\n) -> anyhow::Result<Vec<GrepFileMatch>> {\n    let mut matched_files = HashMap::new();\n\n    for line in output.trim().split(\"\\n\") {\n        let mut parts = line.split(\":\");\n        let file = parts.next();\n        let line_number = parts.next();\n\n        let (Some(file), Some(line_number)) = (file, line_number) else {\n            return Err(anyhow::anyhow!(\n                \"Failed to parse Grep output, unexpected format\"\n            ));\n        };\n        let line_number = match line_number.parse::<usize>() {\n            Ok(line_number) => line_number,\n            Err(e) => {\n                return Err(anyhow::anyhow!(\n                    \"Failed to parse line number in Grep output: {:?}\",\n                    e\n                ));\n            }\n        };\n\n        matched_files\n            .entry(file)\n            .or_insert_with(Vec::new)\n            .push(GrepLineMatch { line_number });\n    }","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/warpdotdev/warp/blob/e72fd7aacbbb2236d9b3be2aad7e7178fe94b4bc/app/src/ai/blocklist/action_model/execute/grep.rs#L638-L674","documentation":"The grep output parser expects every line to contain at least 'file:line-number' as the first two colon-separated fields. A line where either the file or line-number slot is missing - blank lines, 'Binary file x matches', banners, or grep diagnostics without that shape - fails the whole parse with this error.","triggerScenarios":"Output passed to the parser contains a line whose split(':') yields fewer than two parts: blank trailing lines, 'Binary file ... matches' entries, command banners, or truncated output (grep.rs:649-658).","commonSituations":"Grep invoked without flags that guarantee the file:line format (missing -n, no --no-messages); binary files matched; stderr or headers mixed into stdout; output from a different grep implementation (BSD/GNU/BusyBox) with different formatting.","solutions":["Invoke grep with flags that pin the format: -n, --no-messages (-s), and -I to skip binaries","Strip blank lines and known banner lines before parsing","Make the parser skip-and-warn on malformed lines instead of failing the entire result","Pin the grep implementation used across platforms"],"exampleFix":"// before\nfor line in output.trim().split(\"\\n\") {\n    let mut parts = line.split(\":\");\n    let (Some(file), Some(line_number)) = (parts.next(), parts.next()) else {\n        return Err(anyhow::anyhow!(\"Failed to parse Grep output, unexpected format\"));\n    };\n}\n\n// after\nfor line in output.trim().split(\"\\n\") {\n    let mut parts = line.split(\":\");\n    let (Some(file), Some(line_number)) = (parts.next(), parts.next()) else {\n        log::warn!(\"Skipping malformed grep line: {line:?}\");\n        continue;\n    };\n}","handlingStrategy":"fallback","validationCode":"let parseable = output.lines().all(|l| {\n    let mut p = l.split(':');\n    matches!((p.next(), p.next()), (Some(f), Some(n)) if !f.is_empty() && n.parse::<usize>().is_ok())\n});\nif !parseable { sanitize_output_before_parsing(output); }","typeGuard":"fn is_grep_match_line(line: &str) -> bool {\n    let mut p = line.split(':');\n    matches!((p.next(), p.next()), (Some(f), Some(n))\n        if !f.is_empty() && !f.starts_with(\"grep\") && n.parse::<usize>().is_ok())\n}","tryCatchPattern":"match parse_grep_output(&output, shell, cwd).await {\n    Err(e) if e.to_string().contains(\"unexpected format\") => {\n        let clean: String = output.lines().filter(|l| is_grep_match_line(l)).collect::<Vec<_>>().join(\"\\n\");\n        parse_grep_output(&clean, shell, cwd).await // retry on filtered output\n    }\n    r => r,\n}","preventionTips":["Always run grep with -n, --no-messages, and -I so output is strictly file:line:content","Trim output and drop blank/binary-file/banner lines before parsing","Prefer skipping malformed lines with a warning over failing the whole search result"],"tags":["rust","warp","grep","parsing","shell"],"backgroundTag":null,"analyzedSha":"e72fd7aacbbb2236d9b3be2aad7e7178fe94b4bc","analyzedAt":"2026-08-16T08:27:25.381Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}