AlexsJones/llmfit · error
fit_to_json returns an object
Error message
fit_to_json returns an object
What it means
Panic from .expect("fit_to_json returns an object") in the CLI's fit_to_json wrapper (display.rs:782-800). It takes the serde_json::Value returned by the shared serializer crate::serve_shared::fit_to_json and calls as_object_mut() so it can overlay legacy keys (fit_level, run_mode, runtime, capabilities) that scripts depend on (see the #759 comment in-source). The expect encodes a cross-module API contract: the shared serializer must return a JSON object; if it ever returns an array, string, null, or a wrapped/versioned envelope, this layer panics.
Source
Thrown at llmfit-tui/src/display.rs:790
"{}",
serde_json::to_string_pretty(&output).expect("JSON serialization failed")
);
}
fn system_json(specs: &SystemSpecs) -> serde_json::Value {
crate::serve_shared::system_json(specs)
}
/// CLI `fit --json` envelope: the shared serializer plus this frontend's legacy
/// overlays. The overlaid keys carry human-readable strings the API/MCP side
/// expresses as machine codes (with the human string under a `*_label` key);
/// the CLI's overloaded values are load-bearing for existing scripts, so they
/// stay put here until a future PR deprecates them (see #759).
fn fit_to_json(fit: &ModelFit) -> serde_json::Value {
let mut value = crate::serve_shared::fit_to_json(fit);
let obj = value
.as_object_mut()
.expect("fit_to_json returns an object");
obj.insert("fit_level".to_string(), serde_json::json!(fit.fit_text()));
obj.insert(
"run_mode".to_string(),
serde_json::json!(fit.run_mode_text()),
);
obj.insert("runtime".to_string(), serde_json::json!(fit.runtime_text()));
obj.insert(
"capabilities".to_string(),
serde_json::json!(
fit.model
.capabilities
.iter()
.map(|c| c.label())
.collect::<Vec<_>>()
),
);
value
}View on GitHub (pinned to acc7e40c3a)
Solutions
- Check git history / recent changes to serve_shared::fit_to_json — this expect only fires when that function stops returning an object.
- Restore the contract: keep serve_shared::fit_to_json returning a JSON object for every ModelFit, or update the CLI overlay in display.rs to handle the new shape (e.g. unwrap the versioned envelope before overlaying).
- Add a unit test asserting serve_shared::fit_to_json(&mock_fit).is_object() so CI catches shape drift.
- If you are consuming the CLI, switch to parsing `llmfit fit --json` output only, which exercises the shared serializer's stable contract.
Example fix
// before
let obj = value
.as_object_mut()
.expect("fit_to_json returns an object");
// after
let Some(obj) = value.as_object_mut() else {
eprintln!("internal error: fit_to_json produced non-object JSON for {}", fit.model.name);
std::process::exit(1);
}; Defensive patterns
Strategy: type-guard
Validate before calling
// Before calling any display_json_fits* helper, verify the shared serializer's contract.
let ok = fits.iter().all(|f| crate::serve_shared::fit_to_json(f).is_object());
if !ok {
eprintln!("shared fit_to_json violated its object contract; skipping JSON output");
return;
}
display::display_json_fits(specs, fits); Type guard
fn shared_fit_json_is_object(fit: &llmfit_core::fit::ModelFit) -> bool {
crate::serve_shared::fit_to_json(fit).is_object()
} Try / catch
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = display::fit_to_json(fit); // overlay path that asserts object shape
}));
if result.is_err() {
eprintln!("error: fit_to_json shape contract broken; this is a bug — please report it");
std::process::exit(1);
} Prevention
- Pin the contract with a test: assert serve_shared::fit_to_json(&mock_fit).is_object() for every runtime variant.
- If versioning the shared serializer, unwrap the envelope inside serve_shared::fit_to_json so callers always receive the bare object.
- Never return Value::Null from serve_shared::fit_to_json as an error sentinel — return Option/Result instead so the object invariant holds.
When it happens
Trigger: Any `llmfit fit --json`-family command after serve_shared::fit_to_json (llmfit-tui/src/serve_shared.rs) is changed to return a non-object Value — e.g. someone wraps the payload for API versioning as {"v2": {...}} or returns serde_json::Value::Null for an unknown model. The related bare .unwrap() in display_json_fits_with_llamacpp (display.rs:528) panics first for the llamacpp variant.
Common situations: Refactors that unify the API/MCP serializer and change its return shape without updating the CLI overlay; adding a null/error sentinel to fit_to_json for models with missing data; the two frontends drifting during parallel feature work.
Related errors
- JSON serialization failed
- valid MLX suffix regex
- embedded benchmarks.yaml is invalid
- JSON serialization failed
- embedded use_case_benchmarks.json is invalid
AI-assisted analysis of AlexsJones/llmfit@acc7e40c3a (2026-08-17).
Data as JSON: /api/errors/e7227f22d39ad9d8.
Report an issue: GitHub.