From d786c9d79bbf7370705d2140acb7398287b3bcd2 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 16 Jul 2026 09:11:16 +0200 Subject: [PATCH] Escape the merge conflicts view before prompting to continue the rebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the last conflict of a file is resolved, a files refresh both offers to continue the rebase/merge (if we started it ourselves) and, via its merge-conflicts scope, escapes from the merge conflicts view back to the files context. The two race: the prompt is bounced onto the UI thread by the files worker, while the escape's context push is queued separately by EscapeMerge, and it deliberately refuses to push the files context over a popup. So if the prompt opens first, the escape does nothing, and closing the prompt lands the user in the stale merge conflicts view — usually already emptied by the escape's state reset — instead of the files panel. No later refresh rescues this. Fix this by escaping from the merge conflicts view right before opening the prompt. This runs on the UI thread and doesn't hold the merge conflicts mutex, so it can reset the state and push the files context synchronously; whichever side runs first, the prompt now always opens on top of the files context, and EscapeMerge's guarded push still does nothing only when that's the right thing to do. This is a timing race with no deterministic regression test; it showed up as a rare flake in tests that cancel the continue prompt (e.g. commit/amend_when_there_are_conflicts_and_continue) when looping the integration tests under the race detector. Co-Authored-By: Claude Fable 5 --- pkg/gui/controllers/helpers/refresh_helper.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index 3c8524ffe..21fdc8a7a 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -1207,6 +1207,19 @@ func (self *RefreshHelper) refreshStateFiles(captured capturedFilesState, env re // for operations we started ourselves; prompting for one that was // started outside lazygit (e.g. by a coding agent) would be confusing. self.onUIThreadUnlessRepoChanged(env, func() error { + // The merge-conflicts scope of this refresh also notices that + // the conflicts are gone and escapes from the merge conflicts + // view to the files context (see RefreshMergeState), but it + // runs concurrently with us, and its escape refuses to push + // the files context over a popup. So if our prompt opens + // first, the escape does nothing, and closing the prompt + // would land the user in the dead merge conflicts view. + // Escape it ourselves before opening the prompt, so that the + // prompt always opens on top of the files context. + if self.c.Context().IsCurrent(self.c.Contexts().MergeConflicts) { + self.mergeConflictsHelper.ResetMergeState() + self.c.Context().Push(self.c.Contexts().Files, types.OnFocusOpts{}) + } return self.mergeAndRebaseHelper.PromptToContinueRebase() }) }