From 8655d3f5a59d35cfc4176d66defed263b62d64a5 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 4 Jul 2026 07:49:04 +0200 Subject: [PATCH] Exclude view-buffer render tasks from the busy query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo-switch busy query must not count view-buffer content rendering: those tasks paint a view rather than drive a git operation, so leaving one running across a switch is harmless (the switch's own refresh re-renders). More importantly, they fire on nearly every focus/selection change — including the context activation that runs right before a menu/prompt confirmation handler (e.g. confirming worktree creation). A synchronous busy check in such a handler would otherwise see that render and make the very switch the handler is about to request refuse itself. Route ViewBufferManager's tasks through a new gocui NewBackgroundTask so they're tracked for idle detection but excluded from the busy query. The task "background" flag now covers two kinds of non-blocking work: the background routines (and their refreshes) tagged earlier, and view rendering. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/gocui/gui.go | 7 +++++++ pkg/gocui/task.go | 12 ++++++++---- pkg/gui/tasks_adapter.go | 9 ++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pkg/gocui/gui.go b/pkg/gocui/gui.go index ff113a2dd..a13744997 100644 --- a/pkg/gocui/gui.go +++ b/pkg/gocui/gui.go @@ -282,6 +282,13 @@ func (g *Gui) NewTask() *TaskImpl { return g.taskManager.NewTask(false) } +// NewBackgroundTask creates a task that is tracked for idle detection but does +// not count towards the program being busy for repo-switch safety. See +// TaskImpl.background. +func (g *Gui) NewBackgroundTask() *TaskImpl { + return g.taskManager.NewTask(true) +} + // Busy reports whether any foreground work is in flight, ignoring the event // currently being processed on the main goroutine (see currentTask). Background // routines (auto-fetch etc.) don't count. It's used to decide whether it's safe diff --git a/pkg/gocui/task.go b/pkg/gocui/task.go index 377781a4f..08a77463f 100644 --- a/pkg/gocui/task.go +++ b/pkg/gocui/task.go @@ -20,10 +20,14 @@ type TaskImpl struct { withMutex func(func()) // Background tasks don't count towards the program being "busy" for the // purpose of deciding whether a repo switch is safe (see - // TaskManager.hasBusyForegroundTaskExcept). They're the ongoing background - // routines (auto-fetch, files refresh, external-change detection) and the - // refreshes they trigger, whose model writes are already guarded against a - // concurrent repo switch by the repo generation. + // TaskManager.hasBusyForegroundTaskExcept). Two kinds of work are tagged + // this way: the ongoing background routines (auto-fetch, files refresh, + // external-change detection) and the refreshes they trigger, whose model + // writes are already guarded against a concurrent repo switch by the repo + // generation; and view-buffer content rendering, which only paints a view + // and so is harmless to leave running across a switch. What stays + // foreground is lazygit driving a git operation and applying its results + // to the model — exactly the work a repo switch must not run underneath. background bool } diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index 09edd2d36..dd7999107 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -136,7 +136,14 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { view.SetOrigin(0, 0) }, func() gocui.Task { - return gui.c.GocuiGui().NewTask() + // A background task: rendering content into a view is display + // work, not lazygit driving a git operation, so it must not + // count towards being busy and block a repo switch. These + // renders fire on nearly every focus/selection change, including + // the context activation that happens right before a menu/prompt + // handler runs (e.g. confirming worktree creation), which would + // otherwise make the switch that handler triggers refuse itself. + return gui.c.GocuiGui().NewBackgroundTask() }, ) gui.viewBufferManagerMap[view.Name()] = manager