Hmbown/CodeWhale · error

ApplyPatchPreflight should serialize

Error message

ApplyPatchPreflight should serialize

What it means

Panic building audit metadata for `apply_patch`: the `ApplyPatchPreflight` struct (derived `Serialize`) is converted with `serde_json::to_value` to emit the `apply_patch.preflight` event. For a derived struct over plain data this conversion is effectively infallible; it fails only if a field is not JSON-representable — non-string map keys or a non-finite float in a newly added field.

Source

Thrown at crates/tui/src/tools/apply_patch.rs:697

    Ok(ApplyPatchPreflight {
        files_total: changes.len(),
        touched_files,
        hunks_total: 0,
        creates: Vec::new(),
        deletes: Vec::new(),
        path_override: None,
        header_path_mismatch: None,
    })
}

fn apply_patch_result_metadata(
    preflight: &ApplyPatchPreflight,
    pending: &[PendingWrite],
    stats: &PatchStatsExt,
) -> Value {
    let mut metadata =
        serde_json::to_value(preflight).expect("ApplyPatchPreflight should serialize");
    if let Some(object) = metadata.as_object_mut() {
        object.insert("event".to_string(), json!("apply_patch.preflight"));
        object.insert(
            "mutation".to_string(),
            build_mutation_metadata(pending, &stats.file_summaries),
        );
    }
    metadata
}

/// Preserve the exact applied before/after diff independently from approval
/// presentation. The TUI consumes this success-only metadata for its calm
/// File receipt; the normal model-facing result remains compact JSON.
fn build_mutation_metadata(pending: &[PendingWrite], summaries: &[FileSummary]) -> Value {
    let mut matched = HashSet::new();
    let mut renames = Vec::new();

    for (delete_index, (deleted, delete_summary)) in pending.iter().zip(summaries).enumerate() {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Change the offending field to string-keyed maps (`BTreeMap<String, _>`) or sanitize floats, then rebuild.
  2. Convert the expect to `.context(...)?` so the failure propagates into the patch tool's error path instead of crashing the turn.
  3. Add a unit test that round-trips a populated preflight through `serde_json::to_value`.
  4. Keep the struct JSON-shaped by construction: only String/number/bool/Vec/Option fields and string-keyed maps.

Example fix

// before
serde_json::to_value(preflight).expect("ApplyPatchPreflight should serialize")

// after: propagate into the tool's error path
let mut metadata = serde_json::to_value(preflight)
    .context("serialize ApplyPatchPreflight for audit metadata")?;
Defensive patterns

Strategy: validation

Validate before calling

// Prove the preflight serializes before emitting the audit event
if serde_json::to_value(&preflight).is_err() {
    return emit_without_preflight_metadata();
}

Type guard

fn json_shaped(v: &serde_json::Value) -> bool {
    match v {
        serde_json::Value::Number(n) => n.as_f64().map_or(true, f64::is_finite),
        serde_json::Value::Array(a) => a.iter().all(json_shaped),
        serde_json::Value::Object(o) => o.values().all(json_shaped),
        _ => true,
    }
}

Prevention

When it happens

Trigger: Adding a field to `ApplyPatchPreflight` typed as a non-string-keyed map (`HashMap<u32, _>`, `HashMap<PathBuf, _>`) or carrying `f64::NAN`; the first apply_patch call after that build panics while emitting preflight metadata.

Common situations: Schema growth on the preflight struct during patch-tool development; copying types from a binary/TOML config context into the JSON metadata context without adjusting key types.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/b22e6205b7f89366. Report an issue: GitHub.