{"record":{"id":"35e24dc3a7dda7c2","repo":"AlexsJones/llmfit","slug":"csv-serialization-failed","errorCode":null,"errorMessage":"CSV serialization failed","messagePattern":"CSV serialization failed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"llmfit-tui/src/display.rs","lineNumber":1039,"sourceCode":"                score: round1(fit.score),\n                score_quality: round1(fit.score_components.quality),\n                score_speed: round1(fit.score_components.speed),\n                score_fit: round1(fit.score_components.fit),\n                score_context: round1(fit.score_components.context),\n                estimated_tps: round1(fit.estimated_tps),\n                memory_required_gb: round2(fit.memory_required_gb),\n                memory_available_gb: round2(fit.memory_available_gb),\n                utilization_pct: round1(fit.utilization_pct),\n                disk_size_gb: round2(fit.model.estimate_disk_gb(&fit.best_quant)),\n                best_quant: fit.best_quant.clone(),\n                runtime: fit.runtime.label().to_string(),\n                use_case: fit.use_case.label().to_string(),\n                release_date: fit.model.release_date.clone(),\n                license: fit.model.license.clone(),\n                is_moe: fit.model.is_moe,\n                installed: fit.installed,\n            })\n            .expect(\"CSV serialization failed\");\n    }\n\n    writer.flush().expect(\"CSV flush failed\");\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use llmfit_core::fit::{FitLevel, InferenceRuntime, ScoreComponents};\n    use llmfit_core::models::{Capability, GgufSource, ModelFormat, UseCase};\n\n    fn mock_fit(run_mode: RunMode, use_case: UseCase, model_use_case: &str) -> ModelFit {\n        ModelFit {\n            model: LlmModel {\n                name: \"test/model-7b\".to_string(),\n                provider: \"test\".to_string(),\n                parameter_count: \"7B\".to_string(),\n                parameters_raw: None,","sourceCodeStart":1021,"sourceCodeEnd":1057,"githubUrl":"https://github.com/AlexsJones/llmfit/blob/8f16394d7478d03a12937d50eec41b39311052d8/llmfit-tui/src/display.rs#L1021-L1057","documentation":"Panic from .expect(\"CSV serialization failed\") on csv::Writer::serialize(CsvFitRow {...}) in display_csv_fits (display.rs:957-989), which backs `llmfit fit --csv` (main.rs:1131/1526). CsvFitRow is flat (strings, f64s, bools, Options), so CSV field serialization itself cannot fail; the realistic failure is the underlying std::io::stdout() writer returning an IO error — overwhelmingly EPIPE when the downstream pipe consumer (head, grep -q, awk with exit, a closed pager) closes stdout before all rows are written. Because Rust ignores SIGPIPE by default, the EPIPE surfaces as a csv::Error and the expect turns it into a panic.","triggerScenarios":"`llmfit fit --csv | head -n 5`, `llmfit fit --csv | grep -q some-model`, or piping CSV into any command that exits before consuming all ~33+ model rows; also redirecting stdout to a full disk or a revoked network mount.","commonSituations":"Shell one-liners sampling the CSV with head/take-style tools; CI scripts that pipe the CSV into a matcher with -m1; running the command in a terminal whose pager is killed mid-output; writing to a file on a full filesystem.","solutions":["Redirect to a file instead of piping: `llmfit fit --csv > models.csv`, then sample the file with head afterwards.","Use llmfit's own row limiting to avoid early-exit pipes: `llmfit fit --csv -n 5` prints only 5 rows so `| head` is unnecessary.","If you must pipe, let the consumer read everything (e.g. `grep pattern` without -q) or use `set -o pipefail`-aware wrappers that tolerate exit code 141.","Check disk space and mount health when stdout is redirected to a file.","As a maintainer, handle csv::ErrorKind::Io(BrokenPipe) with std::process::exit(141) instead of expect, and consider restoring default SIGPIPE handling in main."],"exampleFix":"// before\nwriter.serialize(row).expect(\"CSV serialization failed\");\n\n// after\nif let Err(e) = writer.serialize(row) {\n    if let csv::ErrorKind::Io(io_err) = e.kind() {\n        if io_err.kind() == std::io::ErrorKind::BrokenPipe {\n            std::process::exit(141); // consumer closed the pipe; not an error\n        }\n    }\n    eprintln!(\"error: CSV serialization failed: {e}\");\n    std::process::exit(1);\n}","handlingStrategy":"fallback","validationCode":"# Shell-level: avoid the early-exit pipe that triggers EPIPE — write to a file instead.\nllmfit fit --csv > /tmp/llmfit-models.csv   # then: head -n 5 /tmp/llmfit-models.csv\n# Or limit rows at the source so no pipe truncation is needed:\n#   llmfit fit --csv -n 5","typeGuard":null,"tryCatchPattern":"// Keep the panic off the user's screen and map it to the conventional SIGPIPE exit code.\nlet result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    display::display_csv_fits(&fits);\n}));\nif result.is_err() {\n    // Distinguish broken pipe from real failures before reporting.\n    eprintln!(\"error: CSV output aborted (broken pipe or write failure)\");\n    std::process::exit(141);\n}","preventionTips":["Never pipe `llmfit fit --csv` into head/grep -q/awk-exit; redirect to a file and post-process it.","Use the -n limit flag to bound rows at the source instead of truncating the pipe.","When scripting, check free space on the redirect target volume before long CSV runs.","Maintainers: exit(141) on csv::ErrorKind::Io(BrokenPipe) instead of expect, and consider restoring default SIGPIPE disposition in main()."],"tags":["rust","csv","broken-pipe","stdout","panic"],"backgroundTag":"broken-pipe","analyzedSha":"8f16394d7478d03a12937d50eec41b39311052d8","analyzedAt":"2026-08-17T10:35:29.658Z","contentChangedAt":"2026-08-17T10:35:29.658Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}