{"record":{"id":"636a4aff3b6f5d86","repo":"gitbutlerapp/gitbutler","slug":"just-set-the-value","errorCode":null,"errorMessage":"just set the value","messagePattern":"just set the value","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-ctx/src/lib.rs","lineNumber":604,"sourceCode":"    ) -> anyhow::Result<(\n        cell::Ref<'_, gix::Repository>,\n        cell::RefMut<'_, but_graph::Workspace>,\n        cell::RefMut<'_, but_db::DbHandle>,\n    )> {\n        let repo = self.repo.get()?;\n        if let Ok(cached) =\n            cell::RefMut::filter_map(self.workspace.try_borrow_mut()?, |opt| opt.as_mut())\n        {\n            let db = self.db.get_cache_mut()?;\n            return Ok((repo, cached, db));\n        }\n        let ws = self.workspace_from_head()?;\n        {\n            let mut value = self.workspace.try_borrow_mut()?;\n            *value = Some(ws);\n        }\n        let ws = cell::RefMut::filter_map(self.workspace.borrow_mut(), |opt| opt.as_mut())\n            .unwrap_or_else(|_| unreachable!(\"just set the value\"));\n        let db = self.db.get_cache_mut()?;\n        Ok((repo, ws, db))\n    }\n\n    /// Create a new cached workspace as seen from the current HEAD for *reading* and return it,\n    /// along with `(guard, &repo, &mut ws, &mut db)`.\n    /// The `db` is writable as this is more useful and naturally synced.\n    /// The guard is for shared access to the repository.\n    ///\n    /// # IMPORTANT\n    /// * if the workspace was changed, write it back into `&mut ws`.\n    /// * Keep the guard alive like `let (_guard, …) = …`!\n    #[instrument(name = \"Context::workspace_and_db_mut\", level = \"debug\", skip_all)]\n    #[expect(clippy::type_complexity)]\n    pub fn workspace_and_db_mut(\n        &self,\n    ) -> anyhow::Result<(\n        RepoSharedGuard,","sourceCodeStart":586,"sourceCodeEnd":622,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-ctx/src/lib.rs#L586-L622","documentation":"This accessor (but-ctx/src/lib.rs:604) caches a workspace derived from HEAD in a `RefCell<Option<Workspace>>`: on cache miss it computes `workspace_from_head()`, stores `Some(ws)` with `try_borrow_mut()`, then re-borrows and `filter_map`s the `Some` out. The `unreachable!(\"just set the value\")` can only fail if the cell is empty right after being written or the `RefCell` is already borrowed — the write makes emptiness impossible, so this is a defensive marker for reentrancy bugs rather than an expected error.","triggerScenarios":"Reentrant access that mutably borrows `ctx.workspace` between the store and the re-read; calling Context workspace methods from within a callback invoked while another accessor's `RefMut` is alive; multiple threads sharing one non-Sync Context instance across `catch_unwind`/FFI boundaries.","commonSituations":"Deep call stacks where a workspace mutation triggers events that re-enter Context accessors; debug builds with additional borrow checking; API-boundary refactors that hold workspace borrows across user callbacks.","solutions":["Never call other `Context` workspace methods while holding a `Ref`/`RefMut` returned by these accessors; scope borrows tightly (`let (_guard, ..)` blocks).","Move callbacks/event emission outside the borrow scope so reentrancy cannot interleave with the cache fill.","If hit, reproduce with a backtrace and identify the overlapping accessor pair, then restructure to sequential calls."],"exampleFix":"// before\nlet (repo, ws, db) = ctx.workspace_mut_from_head()?;\nnotify_ui(&ws); // callback re-enters ctx accessors while ws (RefMut) is alive\n\n// after\nlet result = {\n    let (_guard, repo, ws, db) = ctx.workspace_mut_from_head()?;\n    compute(&repo, &ws, db)?\n}; // borrow released here\nnotify_ui(&result); // reentrancy-safe","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Scope every (guard, repo, ws, db) borrow to the smallest block; drop it before callbacks or awaits.","Never re-enter Context workspace accessors while a returned Ref/RefMut is alive.","Pass the pre-borrowed tuple into helpers instead of &Context to make reentrancy impossible."],"tags":["rust","but-ctx","refcell","reentrancy","internal-invariant","cache"],"backgroundTag":"refcell-borrow-violation","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}