AlexsJones/llmfit · error

CSV serialization failed

Error message

CSV serialization failed

What it means

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.

Source

Thrown at llmfit-tui/src/display.rs:1039

                score: round1(fit.score),
                score_quality: round1(fit.score_components.quality),
                score_speed: round1(fit.score_components.speed),
                score_fit: round1(fit.score_components.fit),
                score_context: round1(fit.score_components.context),
                estimated_tps: round1(fit.estimated_tps),
                memory_required_gb: round2(fit.memory_required_gb),
                memory_available_gb: round2(fit.memory_available_gb),
                utilization_pct: round1(fit.utilization_pct),
                disk_size_gb: round2(fit.model.estimate_disk_gb(&fit.best_quant)),
                best_quant: fit.best_quant.clone(),
                runtime: fit.runtime.label().to_string(),
                use_case: fit.use_case.label().to_string(),
                release_date: fit.model.release_date.clone(),
                license: fit.model.license.clone(),
                is_moe: fit.model.is_moe,
                installed: fit.installed,
            })
            .expect("CSV serialization failed");
    }

    writer.flush().expect("CSV flush failed");
}

#[cfg(test)]
mod tests {
    use super::*;
    use llmfit_core::fit::{FitLevel, InferenceRuntime, ScoreComponents};
    use llmfit_core::models::{Capability, GgufSource, ModelFormat, UseCase};

    fn mock_fit(run_mode: RunMode, use_case: UseCase, model_use_case: &str) -> ModelFit {
        ModelFit {
            model: LlmModel {
                name: "test/model-7b".to_string(),
                provider: "test".to_string(),
                parameter_count: "7B".to_string(),
                parameters_raw: None,

View on GitHub (pinned to 8f16394d74)

Solutions

  1. Redirect to a file instead of piping: `llmfit fit --csv > models.csv`, then sample the file with head afterwards.
  2. Use llmfit's own row limiting to avoid early-exit pipes: `llmfit fit --csv -n 5` prints only 5 rows so `| head` is unnecessary.
  3. 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.
  4. Check disk space and mount health when stdout is redirected to a file.
  5. As a maintainer, handle csv::ErrorKind::Io(BrokenPipe) with std::process::exit(141) instead of expect, and consider restoring default SIGPIPE handling in main.

Example fix

// before
writer.serialize(row).expect("CSV serialization failed");

// after
if let Err(e) = writer.serialize(row) {
    if let csv::ErrorKind::Io(io_err) = e.kind() {
        if io_err.kind() == std::io::ErrorKind::BrokenPipe {
            std::process::exit(141); // consumer closed the pipe; not an error
        }
    }
    eprintln!("error: CSV serialization failed: {e}");
    std::process::exit(1);
}
Defensive patterns

Strategy: fallback

Validate before calling

# Shell-level: avoid the early-exit pipe that triggers EPIPE — write to a file instead.
llmfit fit --csv > /tmp/llmfit-models.csv   # then: head -n 5 /tmp/llmfit-models.csv
# Or limit rows at the source so no pipe truncation is needed:
#   llmfit fit --csv -n 5

Try / catch

// Keep the panic off the user's screen and map it to the conventional SIGPIPE exit code.
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    display::display_csv_fits(&fits);
}));
if result.is_err() {
    // Distinguish broken pipe from real failures before reporting.
    eprintln!("error: CSV output aborted (broken pipe or write failure)");
    std::process::exit(141);
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of AlexsJones/llmfit@8f16394d74 (2026-08-17). Data as JSON: /api/errors/35e24dc3a7dda7c2. Report an issue: GitHub.