{"record":{"id":"070e81222100b8ac","repo":"sinelaw/fresh","slug":"copy-action-without-source","errorCode":null,"errorMessage":"Copy action without source","messagePattern":"Copy action without source","errorType":"exception","errorClass":"io::Error (InvalidData)","httpStatus":null,"severity":"error","filePath":"crates/fresh-editor-core/src/model/buffer/save.rs","lineNumber":532,"sourceCode":"        out_file.write_all(&chunk)?;\n        offset += chunk_len as u64;\n    }\n\n    Ok(())\n}\n\n/// Write the recipe content to a file writer.\npub(super) fn write_recipe_to_file(\n    fs: &Arc<dyn FileSystem + Send + Sync>,\n    out_file: &mut Box<dyn FileWriter>,\n    recipe: &WriteRecipe,\n) -> io::Result<()> {\n    for action in &recipe.actions {\n        match action {\n            RecipeAction::Copy { offset, len } => {\n                // Read from source and write to output\n                let src_path = recipe.src_path.as_ref().ok_or_else(|| {\n                    io::Error::new(io::ErrorKind::InvalidData, \"Copy action without source\")\n                })?;\n                let data = fs.read_range(src_path, *offset, *len as usize)?;\n                out_file.write_all(&data)?;\n            }\n            RecipeAction::Insert { index } => {\n                out_file.write_all(&recipe.insert_data[*index])?;\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Internal helper to create a SudoSaveRequired error.\npub(super) fn make_sudo_error(\n    temp_path: PathBuf,\n    dest_path: &Path,\n    original_metadata: Option<FileMetadata>,\n) -> anyhow::Error {","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor-core/src/model/buffer/save.rs#L514-L550","documentation":"write_recipe_to_file fails when the recipe contains Copy actions but the recipe's src_path is None. Copy actions read bytes from the source file, so without a source path the recipe cannot be executed. This is an internally inconsistent recipe (InvalidData).","triggerScenarios":"Calling write_recipe_to_file with a recipe built with Copy actions but never assigned src_path — e.g. an in-memory-only document saved through the copy-based path instead of the insert-only path.","commonSituations":"Saving a new (never-loaded-from-disk) buffer whose recipe generator wrongly emitted Copy actions; a builder that forgets set_src_path.","solutions":["Set recipe.src_path to the file the buffer was loaded from before writing.","For buffers with no source file, build an insert-only recipe (no Copy actions).","Validate recipes: if actions contain Copy, assert src_path.is_some() at build time."],"exampleFix":"// before\nlet recipe = WriteRecipe { actions: vec![RecipeAction::Copy{offset:0,len:10}], src_path: None, .. };\n// after\nlet recipe = WriteRecipe { actions: vec![RecipeAction::Copy{offset:0,len:10}], src_path: Some(original_path.into()), .. };","handlingStrategy":"validation","validationCode":"if recipe.actions.iter().any(|a| matches!(a, RecipeAction::Copy{..})) && recipe.src_path.is_none() {\n    return Err(io::Error::new(io::ErrorKind::InvalidData, \"recipe has Copy actions but no src_path\"));\n}","typeGuard":null,"tryCatchPattern":"match write_recipe_to_file(&recipe, ...) {\n    Ok(()) => (),\n    Err(e) if e.to_string().contains(\"Copy action without source\") => {\n        // rebuild as insert-only recipe for unsaved buffers\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always set src_path when building a recipe with Copy actions","Use insert-only recipes for buffers with no backing file","Validate recipes at construction time, not at write time"],"tags":["save","io","invariant"],"backgroundTag":"invalid-state-transition","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}