{"record":{"id":"c610e07d1fb86743","repo":"sinelaw/fresh","slug":"buffer-not-found-save","errorCode":null,"errorMessage":"Buffer {} not found","messagePattern":"Buffer (.+?) not found","errorType":"exception","errorClass":"io::Error (InvalidData)","httpStatus":null,"severity":"error","filePath":"crates/fresh-editor-core/src/model/buffer/save.rs","lineNumber":196,"sourceCode":"            .file_path()\n            .filter(|p| persistence.fs().exists(p))\n    };\n    let target_ending = format.line_ending();\n    let target_encoding = format.encoding();\n\n    let mut insert_data: Vec<Vec<u8>> = Vec::new();\n    let mut actions: Vec<RecipeAction> = Vec::new();\n\n    // Add BOM as the first piece if the target encoding has one\n    if let Some(bom) = target_encoding.bom_bytes() {\n        insert_data.push(bom.to_vec());\n        actions.push(RecipeAction::Insert { index: 0 });\n    }\n\n    for piece_view in piece_tree.iter_pieces_in_range(0, total) {\n        let buffer_id = piece_view.location.buffer_id();\n        let buffer = buffers.get(buffer_id).ok_or_else(|| {\n            io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"Buffer {} not found\", buffer_id),\n            )\n        })?;\n\n        match &buffer.data {\n            // Unloaded buffer: can use Copy if same source file, else load and send\n            BufferData::Unloaded {\n                file_path,\n                file_offset,\n                ..\n            } => {\n                // Can only use Copy if:\n                // - This is a Stored piece (original file content)\n                // - We have a valid source for copying\n                // - This buffer is from that source\n                // - No line ending or encoding conversion needed\n                let can_copy = matches!(piece_view.location, BufferLocation::Stored(_))","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor-core/src/model/buffer/save.rs#L178-L214","documentation":"build_write_recipe fails while walking pieces of the piece tree when a piece references a buffer_id missing from the buffers map. Since the recipe must copy bytes from each source buffer, a missing buffer makes the save recipe unbuildable. Reported as InvalidData because the recipe input is inconsistent.","triggerScenarios":"Calling build_write_recipe (via save) where iter_pieces_in_range yields a piece whose location.buffer_id() is not in `buffers` — typically after a buffer was unloaded/removed while still referenced by pieces.","commonSituations":"Saving a document assembled from pieces of a since-closed buffer; lifecycle bug where removal precedes save.","solutions":["Keep referenced buffers alive until the save completes (defer removal).","Log which buffer_id is missing and fix the code path that removed it prematurely.","Validate all piece buffer_ids before building the recipe."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"for piece in piece_tree.iter_pieces_in_range(0, total) {\n    assert!(buffers.contains_key(piece.location.buffer_id()), \"piece references removed buffer\");\n}","typeGuard":null,"tryCatchPattern":"match build_write_recipe(...) {\n    Ok(recipe) => recipe,\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {\n        // keep buffers alive until save completes, then retry\n        reload_buffers();\n        build_write_recipe(...)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Defer buffer removal until in-flight saves complete","Assert all piece buffer_ids exist before building recipes","Log the missing buffer_id for lifecycle debugging"],"tags":["buffers","save","io"],"backgroundTag":"internal-invariant-violation","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}