From 5162a768eb4c23531acbdf89d0519f6918060d9d Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Mon, 6 Jul 2026 16:20:11 +0200 Subject: [PATCH] Guard every refresh's entry point, not just the commits scope With every scope's worker reads now captured on the UI thread and every worker caller on RefreshFromWorker, the debug entry-point assertion no longer needs to be scoped to the commits refresh. Move it to the top of performRefresh so it guards every refresh regardless of which scopes it touches, and drop the per-scope gate. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/gui/controllers/helpers/refresh_helper.go | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index cbf388d0a..65870ca20 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -119,13 +119,13 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr // runs inline or has to hop (see captureOnUIThread). fRunsOnUIThread := options.Mode == types.BLOCK_UI || !calledFromWorker - // Record the caller's own goroutine now, before a BLOCK_UI refresh dispatches - // f onto the UI thread, so the debug assertion below can verify the caller - // picked the entry point matching its thread regardless of the mode. Only - // read in debug (goid stays out of production control flow). - callerIsUIThread := false - if self.c.GetConfig().GetDebug() { - callerIsUIThread = self.c.GocuiGui().IsUIThread() + // Debug-only guard: every refresh must be issued from the entry point that + // matches its goroutine — Refresh on the UI thread, RefreshFromWorker on a + // worker. We check the caller's own goroutine here, before a BLOCK_UI + // refresh dispatches f onto the UI thread, so it holds regardless of the + // mode. goid stays out of production control flow (debug only). + if self.c.GetConfig().GetDebug() && self.c.GocuiGui().IsUIThread() == calledFromWorker { + panic("Refresh called from a worker, or RefreshFromWorker called from the UI thread") } f := func() { @@ -212,18 +212,6 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr var loadedRemotes []*models.Remote includeWorktreesWithBranches := false if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) { - // Debug-only guard: the caller must have picked the entry point that - // matches its goroutine — Refresh on the UI thread, RefreshFromWorker - // on a worker. We check the caller's own thread (captured above, - // before any BLOCK_UI dispatch), so it holds regardless of the mode. - // It's scoped to the commits refresh for now, the one scope whose - // worker reads have moved to the UI-thread capture below; once the - // other scopes are converted too it can move up to guard every - // refresh unconditionally. - if self.c.GetConfig().GetDebug() && callerIsUIThread == calledFromWorker { - panic("Refresh called from a worker, or RefreshFromWorker called from the UI thread") - } - // whenever we change commits, we should update branches because the upstream/downstream // counts can change. Whenever we change branches we should also change commits // e.g. in the case of switching branches.