BloopAI/vibe-kanban · error

Scratch serialization should not fail

Error message

Scratch serialization should not fail

What it means

This panic comes from `serde_json::to_value(scratch).expect(...)` inside `Patch::add` (crates/services/src/services/events/patches.rs:108). `serde_json::to_value` only fails when a type's Serialize impl errors — e.g. a custom serializer emitting a non-string map key or propagating an inner serialization error. The expectation is that `Scratch` is plain serializable data, so this indicates a broken Serialize implementation for `Scratch` or one of its payload variants.

Source

Thrown at crates/services/src/services/events/patches.rs:108

                .expect("Workspace path should be valid"),
        })])
    }
}

/// Helper functions for creating scratch-specific patches.
/// All patches use path "/scratch" - filtering is done by matching id and payload type in the value.
pub mod scratch_patch {
    use super::*;

    const SCRATCH_PATH: &str = "/scratch";

    /// Create patch for adding a new scratch
    pub fn add(scratch: &Scratch) -> Patch {
        Patch(vec![PatchOperation::Add(AddOperation {
            path: SCRATCH_PATH
                .try_into()
                .expect("Scratch path should be valid"),
            value: serde_json::to_value(scratch).expect("Scratch serialization should not fail"),
        })])
    }

    /// Create patch for updating an existing scratch
    pub fn replace(scratch: &Scratch) -> Patch {
        Patch(vec![PatchOperation::Replace(ReplaceOperation {
            path: SCRATCH_PATH
                .try_into()
                .expect("Scratch path should be valid"),
            value: serde_json::to_value(scratch).expect("Scratch serialization should not fail"),
        })])
    }

    /// Create patch for removing a scratch.
    /// Uses Replace with deleted marker so clients can filter by id and payload type.
    pub fn remove(scratch_id: Uuid, scratch_type_str: &str) -> Patch {
        Patch(vec![PatchOperation::Replace(ReplaceOperation {
            path: SCRATCH_PATH

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Inspect the Scratch value (especially `payload`) for data serde_json cannot represent (non-string map keys, NaN/Infinity floats); sanitize or remap such data before constructing the Scratch.
  2. Review the `Scratch`/payload Serialize impls (including custom ones) and fix any that can return Err so serialization is total.
  3. Temporarily replace `.expect` with `match`/`unwrap_err` logging to capture the real serde error message and target the failing field.
  4. If a third-party payload type is the culprit, wrap it with a lossy serializer (`serde_json::to_value(&val).unwrap_or(Value::Null)`-style) or convert it to plain serde_json::Value before embedding.

Example fix

// before
value: serde_json::to_value(scratch).expect("Scratch serialization should not fail"),
// after
value: serde_json::to_value(scratch).unwrap_or_else(|e| {
    tracing::error!(error = ?e, "failed to serialize scratch");
    serde_json::Value::Null
}),
Defensive patterns

Strategy: type-guard

Validate before calling

// Pre-flight check before building the patch:
fn scratch_serializable(scratch: &Scratch) -> bool {
    serde_json::to_value(scratch).is_ok()
}

Type guard

fn is_json_safe(v: &serde_json::Value) -> bool {
    use serde_json::Value::*;
    match v {
        Null | Bool(_) | Number(_) | String(_) => v.as_f64().map(|f| f.is_finite()).unwrap_or(true),
        Array(a) => a.iter().all(is_json_safe),
        Object(o) => o.values().all(is_json_safe),
    }
}

Try / catch

// .expect panics aren't catchable; prefer non-panicking construction:
match serde_json::to_value(scratch) {
    Ok(value) => { /* build PatchOperation::Add */ }
    Err(e) => tracing::error!(error = ?e, "scratch serialization failed"),
}

Prevention

When it happens

Trigger: Calling `Patch::add(&scratch)` where serializing the `Scratch` (or its `payload`, including `scratch_type_str`-tagged payload types) returns `Err` — typically because a nested payload uses a serialization-impossible construct (non-string map keys, custom Serialize impls returning errors, f64 NaN/Infinity with restricted serde settings).

Common situations: Adding a new Scratch payload variant whose Serialize impl returns Err; using HashMap keys that aren't strings; NaN/Infinity floats in payload data; a downstream dependency changing serde behavior for a payload field.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/5816b595cf564ab3. Report an issue: GitHub.