gitbutlerapp/gitbutler · error

just set the value

Error message

just set the value

What it means

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.

Source

Thrown at crates/but-ctx/src/lib.rs:604

    ) -> anyhow::Result<(
        cell::Ref<'_, gix::Repository>,
        cell::RefMut<'_, but_graph::Workspace>,
        cell::RefMut<'_, but_db::DbHandle>,
    )> {
        let repo = self.repo.get()?;
        if let Ok(cached) =
            cell::RefMut::filter_map(self.workspace.try_borrow_mut()?, |opt| opt.as_mut())
        {
            let db = self.db.get_cache_mut()?;
            return Ok((repo, cached, db));
        }
        let ws = self.workspace_from_head()?;
        {
            let mut value = self.workspace.try_borrow_mut()?;
            *value = Some(ws);
        }
        let ws = cell::RefMut::filter_map(self.workspace.borrow_mut(), |opt| opt.as_mut())
            .unwrap_or_else(|_| unreachable!("just set the value"));
        let db = self.db.get_cache_mut()?;
        Ok((repo, ws, db))
    }

    /// Create a new cached workspace as seen from the current HEAD for *reading* and return it,
    /// along with `(guard, &repo, &mut ws, &mut db)`.
    /// The `db` is writable as this is more useful and naturally synced.
    /// The guard is for shared access to the repository.
    ///
    /// # IMPORTANT
    /// * if the workspace was changed, write it back into `&mut ws`.
    /// * Keep the guard alive like `let (_guard, …) = …`!
    #[instrument(name = "Context::workspace_and_db_mut", level = "debug", skip_all)]
    #[expect(clippy::type_complexity)]
    pub fn workspace_and_db_mut(
        &self,
    ) -> anyhow::Result<(
        RepoSharedGuard,

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Never call other `Context` workspace methods while holding a `Ref`/`RefMut` returned by these accessors; scope borrows tightly (`let (_guard, ..)` blocks).
  2. Move callbacks/event emission outside the borrow scope so reentrancy cannot interleave with the cache fill.
  3. If hit, reproduce with a backtrace and identify the overlapping accessor pair, then restructure to sequential calls.

Example fix

// before
let (repo, ws, db) = ctx.workspace_mut_from_head()?;
notify_ui(&ws); // callback re-enters ctx accessors while ws (RefMut) is alive

// after
let result = {
    let (_guard, repo, ws, db) = ctx.workspace_mut_from_head()?;
    compute(&repo, &ws, db)?
}; // borrow released here
notify_ui(&result); // reentrancy-safe
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/636a4aff3b6f5d86. Report an issue: GitHub.