{"record":{"id":"15abc08faceb5bce","repo":"lapce/lapce","slug":"can-t-save-to-read-only-file","errorCode":null,"errorMessage":"can't save to read only file","messagePattern":"can't save to read only file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lapce-proxy/src/buffer.rs","lineNumber":67,"sourceCode":"        };\n        let rope = Rope::from(s);\n        let rev = u64::from(!rope.is_empty());\n        let language_id = language_id_from_path(&path).unwrap_or(\"\");\n        let mod_time = get_mod_time(&path);\n        Buffer {\n            id,\n            rope,\n            read_only,\n            path,\n            language_id,\n            rev,\n            mod_time,\n        }\n    }\n\n    pub fn save(&mut self, rev: u64, create_parents: bool) -> Result<()> {\n        if self.read_only {\n            return Err(anyhow!(\"can't save to read only file\"));\n        }\n\n        if self.rev != rev {\n            return Err(anyhow!(\"not the right rev\"));\n        }\n        let bak_extension = self.path.extension().map_or_else(\n            || OsString::from(\"bak\"),\n            |ext| {\n                let mut ext = ext.to_os_string();\n                ext.push(\".bak\");\n                ext\n            },\n        );\n        let path = if self.path.is_symlink() {\n            self.path.canonicalize()?\n        } else {\n            self.path.clone()\n        };","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/lapce/lapce/blob/c9e4c33948033f10f003991a037d949a708eedf8/lapce-proxy/src/buffer.rs#L49-L85","documentation":"Raised by Buffer::save() in lapce-proxy/src/buffer.rs:67 when the buffer was constructed with read_only=true. Read-only buffers are Lapce's in-memory virtual documents (settings editor JSON, keymap views, welcome/preview panes, plugin-generated content) that are not backed by a writable file, so save() refuses before touching the filesystem or checking rev.","triggerScenarios":"A save command (Ctrl+S / workspace save / plugin RPC 'save' with the buffer id) reaches a buffer whose read_only flag is set — e.g. the cursor sits in the settings editor or another virtual document and the keybinding routes to Buffer::save rather than a save-as flow.","commonSituations":"Plugins or editor commands calling document save on virtual buffer ids; older keybinding setups wiring plain save into the settings pane; UI flows that forgot to branch on document type.","solutions":["Route read-only docs to Save As / a target-path dialog instead of save()","Check the buffer's read_only flag before issuing the save RPC","If the file should be editable, open the real file path instead of the virtual buffer","Plugins: only call save on ids obtained from opened-file lifecycles, not virtual ones"],"exampleFix":"// before\nbuffer.save(rev, create_parents)?; // errors with \"can't save to read only file\" on virtual docs\n\n// after\nif buffer.read_only {\n    return Err(anyhow!(\"can't save to read only file: open a real path or use save-as\"));\n}\nbuffer.save(rev, create_parents)?;","handlingStrategy":"validation","validationCode":"// Before saving, branch on the buffer kind (Lapce proxy side)\nif buffer.read_only {\n    // route to save-as with a target path instead of save()\n    return propose_save_as(buffer.id);\n}\nbuffer.save(rev, create_parents)?;","typeGuard":"fn is_writable_buffer(b: &Buffer) -> bool {\n    !b.read_only && b.path.as_ref().map_or(false, |p| p.is_absolute())\n}","tryCatchPattern":"match buffer.save(rev, create_parents) {\n    Err(e) if e.to_string().contains(\"read only\") => propose_save_as(buffer.id),\n    other => other?,\n}","preventionTips":["Check the read_only flag on every save path (commands, keymaps, plugin RPCs)","Virtual documents should always be wired to save-as, never plain save"],"tags":["editor","buffer","read-only","rust"],"backgroundTag":null,"analyzedSha":"c9e4c33948033f10f003991a037d949a708eedf8","analyzedAt":"2026-08-16T11:46:13.210Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}