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
- 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.
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
- 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.
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
- FATAL: Couldn't open in-memory URL: {path_err}
- BUG: {id} is queued
- candidate turns always have review or branch evidence
- validated AI responses only produce content picks
- we never return these as the status iteration is configured
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/636a4aff3b6f5d86.
Report an issue: GitHub.