Repository: jesseduffield/lazygit (language: go)

Issue:
Better global error handling
Right now, all entry points into the codebase (e.g. event handlers) need to catch errors and present them in an error panel, so we have tons of calls to `self.c.Error(err)` in the controller and helper classes. It's easy to forget this, however, and simply return the error instead, which will cause a panic in gocui (see #3490 for an example).

It would be much nicer if we could simply return errors everywhere, and gocui would take care of showing an error box after handling an event that returned an error. To do this `gocui.Gui` just needs an error callback, this seems very easy (see also the last paragraph of [this long comment](https://github.com/jesseduffield/lazygit/pull/1086#discussion_r528259281), starting with "As for surfacing the error..."). It seems we could then hopefully get rid of `IPopupHandler.Error`.


Discussion / hints:

Fix patch:
diff --git a/go.mod b/go.mod
index 8993d750489..2768d0815dd 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,7 @@ require (
 	github.com/integrii/flaggy v1.4.0
 	github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
 	github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
-	github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de
+	github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513
 	github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
 	github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
 	github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -47,7 +47,7 @@ require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/emirpasic/gods v1.12.0 // indirect
 	github.com/fatih/color v1.9.0 // indirect
-	github.com/gdamore/encoding v1.0.0 // indirect
+	github.com/gdamore/encoding v1.0.1 // indirect
 	github.com/go-git/gcfg v1.5.0 // indirect
 	github.com/go-git/go-billy/v5 v5.0.0 // indirect
 	github.com/go-logfmt/logfmt v0.5.0 // indirect
@@ -73,8 +73,8 @@ require (
 	github.com/xanzy/ssh-agent v0.2.1 // indirect
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
 	golang.org/x/net v0.7.0 // indirect
-	golang.org/x/sys v0.18.0 // indirect
-	golang.org/x/term v0.18.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/term v0.19.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
diff --git a/go.sum b/go.sum
index ee6c9cfbb29..e9b35569200 100644
--- a/go.sum
+++ b/go.sum
@@ -87,8 +87,9 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
 github.com/fsmiamoto/git-todo-parser v0.0.5 h1:Bhzd/vz/6Qm3udfkd6NO9fWfD3TpwR9ucp3N75/J5I8=
 github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
 github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
+github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
+github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
 github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
 github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
 github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
@@ -187,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
-github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de h1:2ww1SWgakihE8hFxZ7L3agVeGpA6qwW5vdnhFUXKMQo=
-github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
+github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513 h1:Y1bw5iItrsDCumATc/rklIJ/6K+68ieiWZJedhrNuXo=
+github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -469,14 +470,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/gui/background.go b/pkg/gui/background.go
index 342e3127ecb..db267c0dcae 100644
--- a/pkg/gui/background.go
+++ b/pkg/gui/background.go
@@ -87,9 +87,10 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
 				if self.pauseBackgroundRefreshes {
 					continue
 				}
-				self.gui.c.OnWorker(func(gocui.Task) {
+				self.gui.c.OnWorker(func(gocui.Task) error {
 					_ = function()
 					done <- struct{}{}
+					return nil
 				})
 				// waiting so that we don't bunch up refreshes if the refresh takes longer than the interval
 				<-done
diff --git a/pkg/gui/context/menu_context.go b/pkg/gui/context/menu_context.go
index 6d87d2bb774..e4c3d728024 100644
--- a/pkg/gui/context/menu_context.go
+++ b/pkg/gui/context/menu_context.go
@@ -1,6 +1,8 @@
 package context
 
 import (
+	"errors"
+
 	"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
 	"github.com/jesseduffield/lazygit/pkg/gui/style"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -148,7 +150,7 @@ func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Bin
 func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
 	if selectedItem != nil && selectedItem.DisabledReason != nil {
 		if selectedItem.DisabledReason.ShowErrorInPanel {
-			return self.c.ErrorMsg(selectedItem.DisabledReason.Text)
+			return errors.New(selectedItem.DisabledReason.Text)
 		}
 
 		self.c.ErrorToast(self.c.Tr.DisabledMenuItemPrefix + selectedItem.DisabledReason.Text)
diff --git a/pkg/gui/controllers/basic_commits_controller.go b/pkg/gui/controllers/basic_commits_controller.go
index caff6798305..22e3da9d515 100644
--- a/pkg/gui/controllers/basic_commits_controller.go
+++ b/pkg/gui/controllers/basic_commits_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -172,7 +173,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
 func (self *BasicCommitsController) copyCommitHashToClipboard(commit *models.Commit) error {
 	self.c.LogAction(self.c.Tr.Actions.CopyCommitHashToClipboard)
 	if err := self.c.OS().CopyToClipboard(commit.Hash); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(fmt.Sprintf("'%s' %s", commit.Hash, self.c.Tr.CopiedToClipboard))
@@ -182,12 +183,12 @@ func (self *BasicCommitsController) copyCommitHashToClipboard(commit *models.Com
 func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
 	url, err := self.c.Helpers().Host.GetCommitURL(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
 	if err := self.c.OS().CopyToClipboard(url); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.CommitURLCopiedToClipboard)
@@ -197,12 +198,12 @@ func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Comm
 func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error {
 	diff, err := self.c.Git().Commit.GetCommitDiff(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
 	if err := self.c.OS().CopyToClipboard(diff); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.CommitDiffCopiedToClipboard)
@@ -212,14 +213,14 @@ func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Com
 func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit) error {
 	author, err := self.c.Git().Commit.GetCommitAuthor(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	formattedAuthor := fmt.Sprintf("%s <%s>", author.Name, author.Email)
 
 	self.c.LogAction(self.c.Tr.Actions.CopyCommitAuthorToClipboard)
 	if err := self.c.OS().CopyToClipboard(formattedAuthor); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.CommitAuthorCopiedToClipboard)
@@ -229,12 +230,12 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
 func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
 	message, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
 	if err := self.c.OS().CopyToClipboard(message); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.CommitMessageCopiedToClipboard)
@@ -244,12 +245,12 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
 func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.Commit) error {
 	message, err := self.c.Git().Commit.GetCommitSubject(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.CopyCommitSubjectToClipboard)
 	if err := self.c.OS().CopyToClipboard(message); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.CommitSubjectCopiedToClipboard)
@@ -259,12 +260,12 @@ func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.
 func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
 	url, err := self.c.Helpers().Host.GetCommitURL(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
 	if err := self.c.OS().OpenLink(url); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return nil
@@ -314,7 +315,7 @@ func (self *BasicCommitsController) handleOldCherryPickKey() error {
 			"paste": keybindings.Label(self.c.UserConfig.Keybinding.Commits.PasteCommits),
 		})
 
-	return self.c.ErrorMsg(msg)
+	return errors.New(msg)
 }
 
 func (self *BasicCommitsController) openDiffTool(commit *models.Commit) error {
diff --git a/pkg/gui/controllers/bisect_controller.go b/pkg/gui/controllers/bisect_controller.go
index 89cc6241f7c..0db5701077f 100644
--- a/pkg/gui/controllers/bisect_controller.go
+++ b/pkg/gui/controllers/bisect_controller.go
@@ -96,7 +96,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.BisectMark)
 				if err := self.c.Git().Bisect.Mark(hashToMark, info.NewTerm()); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.afterMark(selectCurrentAfter, waitToReselect)
@@ -109,7 +109,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.BisectMark)
 				if err := self.c.Git().Bisect.Mark(hashToMark, info.OldTerm()); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.afterMark(selectCurrentAfter, waitToReselect)
@@ -122,7 +122,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.BisectSkip)
 				if err := self.c.Git().Bisect.Skip(hashToMark); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.afterMark(selectCurrentAfter, waitToReselect)
@@ -137,7 +137,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.BisectSkip)
 				if err := self.c.Git().Bisect.Skip(commit.Hash); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.afterMark(selectCurrentAfter, waitToReselect)
@@ -169,11 +169,11 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
 				OnPress: func() error {
 					self.c.LogAction(self.c.Tr.Actions.StartBisect)
 					if err := self.c.Git().Bisect.Start(); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 
 					if err := self.c.Git().Bisect.Mark(commit.Hash, info.NewTerm()); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 
 					return self.c.Helpers().Bisect.PostBisectCommandRefresh()
@@ -186,11 +186,11 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
 				OnPress: func() error {
 					self.c.LogAction(self.c.Tr.Actions.StartBisect)
 					if err := self.c.Git().Bisect.Start(); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 
 					if err := self.c.Git().Bisect.Mark(commit.Hash, info.OldTerm()); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 
 					return self.c.Helpers().Bisect.PostBisectCommandRefresh()
@@ -209,7 +209,7 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
 								HandleConfirm: func(newTerm string) error {
 									self.c.LogAction(self.c.Tr.Actions.StartBisect)
 									if err := self.c.Git().Bisect.StartWithTerms(oldTerm, newTerm); err != nil {
-										return self.c.Error(err)
+										return err
 									}
 
 									return self.c.Helpers().Bisect.PostBisectCommandRefresh()
@@ -232,7 +232,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
 
 	formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateHashes)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return self.c.Confirm(types.ConfirmOpts{
@@ -241,7 +241,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.ResetBisect)
 			if err := self.c.Git().Bisect.Reset(); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return self.c.Helpers().Bisect.PostBisectCommandRefresh()
@@ -252,11 +252,11 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
 func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
 	done, candidateHashes, err := self.c.Git().Bisect.IsDone()
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	if err := self.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	if done {
@@ -267,16 +267,17 @@ func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool)
 }
 
 func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
-	selectFn := func() {
+	selectFn := func() error {
 		if selectCurrent {
 			self.selectCurrentBisectCommit()
 		}
+		return nil
 	}
 
 	if waitToReselect {
 		return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
 	} else {
-		selectFn()
+		_ = selectFn()
 
 		return self.c.Helpers().Bisect.PostBisectCommandRefresh()
 	}
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index ff9473ab686..b08ddd0cd76 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -209,7 +209,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
 		LabelColumns: []string{self.c.Tr.UnsetUpstream},
 		OnPress: func() error {
 			if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			if err := self.c.Refresh(types.RefreshOptions{
 				Mode: types.SYNC,
@@ -218,7 +218,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
 					types.COMMITS,
 				},
 			}); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			return nil
 		},
@@ -231,11 +231,11 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
 			return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
 				upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
 				if err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 				if err := self.c.Refresh(types.RefreshOptions{
 					Mode: types.SYNC,
@@ -244,7 +244,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
 						types.COMMITS,
 					},
 				}); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 				return nil
 			})
@@ -279,7 +279,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
 		OnPress: func() error {
 			err := self.c.Helpers().Refs.CreateGitResetMenu(upstream)
 			if err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			return nil
 		},
@@ -292,7 +292,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
 		OpensMenu:    true,
 		OnPress: func() error {
 			if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.ShortUpstreamRefName()); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			return nil
 		},
@@ -334,7 +334,7 @@ func (self *BranchesController) context() *context.BranchesContext {
 
 func (self *BranchesController) press(selectedBranch *models.Branch) error {
 	if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() {
-		return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
+		return errors.New(self.c.Tr.AlreadyCheckedOutBranch)
 	}
 
 	worktreeForRef, ok := self.worktreeForBranch(selectedBranch)
@@ -378,7 +378,7 @@ func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktr
 
 func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
 	if !selectedBranch.IsTrackingRemote() {
-		return self.c.ErrorMsg(self.c.Tr.PullRequestNoUpstream)
+		return errors.New(self.c.Tr.PullRequestNoUpstream)
 	}
 	return self.createPullRequest(selectedBranch.UpstreamBranch, "")
 }
@@ -395,16 +395,16 @@ func (self *BranchesController) copyPullRequestURL() error {
 	branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)
 
 	if !branchExistsOnRemote {
-		return self.c.Error(errors.New(self.c.Tr.NoBranchOnRemote))
+		return errors.New(self.c.Tr.NoBranchOnRemote)
 	}
 
 	url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "")
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL)
 	if err := self.c.OS().CopyToClipboard(url); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.PullRequestURLCopiedToClipboard)
@@ -423,7 +423,7 @@ func (self *BranchesController) forceCheckout() error {
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch)
 			if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
-				_ = self.c.Error(err)
+				return err
 			}
 			return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
 		},
@@ -463,7 +463,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
 	}
 
 	if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.context().SetSelection(0)
@@ -524,7 +524,7 @@ func (self *BranchesController) localDelete(branch *models.Branch) error {
 			return self.forceDelete(branch)
 		}
 		if err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
 	})
@@ -548,7 +548,7 @@ func (self *BranchesController) forceDelete(branch *models.Branch) error {
 		Prompt: message,
 		HandleConfirm: func() error {
 			if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
-				return self.c.ErrorMsg(err.Error())
+				return err
 			}
 			return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
 		},
@@ -615,13 +615,13 @@ func (self *BranchesController) notRebasingOntoSelf(branch *models.Branch) *type
 
 func (self *BranchesController) fastForward(branch *models.Branch) error {
 	if !branch.IsTrackingRemote() {
-		return self.c.ErrorMsg(self.c.Tr.FwdNoUpstream)
+		return errors.New(self.c.Tr.FwdNoUpstream)
 	}
 	if !branch.RemoteBranchStoredLocally() {
-		return self.c.ErrorMsg(self.c.Tr.FwdNoLocalUpstream)
+		return errors.New(self.c.Tr.FwdNoLocalUpstream)
 	}
 	if branch.HasCommitsToPush() {
-		return self.c.ErrorMsg(self.c.Tr.FwdCommitsToPush)
+		return errors.New(self.c.Tr.FwdCommitsToPush)
 	}
 
 	action := self.c.Tr.Actions.FastForwardBranch
@@ -688,7 +688,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
 			HandleConfirm: func(newBranchName string) error {
 				self.c.LogAction(self.c.Tr.Actions.RenameBranch)
 				if err := self.c.Git().Branch.Rename(branch.Name, helpers.SanitizedBranchName(newBranchName)); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				// need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch
@@ -766,7 +766,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
 				LabelColumns: fromToLabelColumns(checkedOutBranch.Name, selectedBranch.Name),
 				OnPress: func() error {
 					if !checkedOutBranch.IsTrackingRemote() || !selectedBranch.IsTrackingRemote() {
-						return self.c.ErrorMsg(self.c.Tr.PullRequestNoUpstream)
+						return errors.New(self.c.Tr.PullRequestNoUpstream)
 					}
 					return self.createPullRequest(checkedOutBranch.UpstreamBranch, selectedBranch.UpstreamBranch)
 				},
@@ -783,13 +783,13 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
 func (self *BranchesController) createPullRequest(from string, to string) error {
 	url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
 
 	if err := self.c.OS().OpenLink(url); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return nil
diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go
index 84e553d8798..4012dc9504b 100644
--- a/pkg/gui/controllers/commit_message_controller.go
+++ b/pkg/gui/controllers/commit_message_controller.go
@@ -1,6 +1,8 @@
 package controllers
 
 import (
+	"errors"
+
 	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
@@ -114,7 +116,7 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
 		if err == git_commands.ErrInvalidCommitIndex {
 			return false, nil
 		}
-		return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
+		return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
 	}
 	if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
 		commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go
index 75524938c19..2b2c709f3db 100644
--- a/pkg/gui/controllers/commits_files_controller.go
+++ b/pkg/gui/controllers/commits_files_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"strings"
 
 	"github.com/jesseduffield/gocui"
@@ -170,7 +171,7 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
 func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
 	self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
 	if err := self.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -179,7 +180,7 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
 func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileNode) error {
 	parentContext, ok := self.c.CurrentContext().GetParentContext()
 	if !ok || parentContext.GetKey() != context.LOCAL_COMMITS_CONTEXT_KEY {
-		return self.c.ErrorMsg(self.c.Tr.CanOnlyDiscardFromLocalCommits)
+		return errors.New(self.c.Tr.CanOnlyDiscardFromLocalCommits)
 	}
 
 	if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
@@ -208,7 +209,7 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
 						return nil
 					})
 					if err != nil {
-						return self.c.Error(err)
+						return err
 					}
 				}
 
@@ -294,7 +295,7 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
 					return patchOperationFunction(file.Name)
 				})
 				if err != nil {
-					return self.c.Error(err)
+					return err
 				}
 			}
 
diff --git a/pkg/gui/controllers/context_lines_controller.go b/pkg/gui/controllers/context_lines_controller.go
index 2dd5382602e..44e191331f1 100644
--- a/pkg/gui/controllers/context_lines_controller.go
+++ b/pkg/gui/controllers/context_lines_controller.go
@@ -65,7 +65,7 @@ func (self *ContextLinesController) Context() types.Context {
 func (self *ContextLinesController) Increase() error {
 	if self.isShowingDiff() {
 		if err := self.checkCanChangeContext(); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 
 		self.c.AppState.DiffContextSize++
@@ -80,7 +80,7 @@ func (self *ContextLinesController) Decrease() error {
 
 	if self.isShowingDiff() && old_size > 1 {
 		if err := self.checkCanChangeContext(); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 
 		self.c.AppState.DiffContextSize = old_size - 1
diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go
index bc37bc2d89a..2d57f0ac039 100644
--- a/pkg/gui/controllers/custom_patch_options_menu_action.go
+++ b/pkg/gui/controllers/custom_patch_options_menu_action.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/jesseduffield/gocui"
@@ -15,11 +16,11 @@ type CustomPatchOptionsMenuAction struct {
 
 func (self *CustomPatchOptionsMenuAction) Call() error {
 	if !self.c.Git().Patch.PatchBuilder.Active() {
-		return self.c.ErrorMsg(self.c.Tr.NoPatchError)
+		return errors.New(self.c.Tr.NoPatchError)
 	}
 
 	if self.c.Git().Patch.PatchBuilder.IsEmpty() {
-		return self.c.ErrorMsg(self.c.Tr.EmptyPatchError)
+		return errors.New(self.c.Tr.EmptyPatchError)
 	}
 
 	menuItems := []*types.MenuItem{
@@ -115,7 +116,7 @@ func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
 
 func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) {
 	if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
-		return false, self.c.ErrorMsg(self.c.Tr.CantPatchWhileRebasingError)
+		return false, errors.New(self.c.Tr.CantPatchWhileRebasingError)
 	}
 	return true, nil
 }
@@ -234,7 +235,7 @@ func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
 	}
 	self.c.LogAction(action)
 	if err := self.c.Git().Patch.ApplyCustomPatch(reverse); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
 }
@@ -244,7 +245,7 @@ func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
 
 	self.c.LogAction(self.c.Tr.Actions.CopyPatchToClipboard)
 	if err := self.c.OS().CopyToClipboard(patch); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Toast(self.c.Tr.PatchCopiedToClipboard)
diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go
index 25d41cd6fc7..b71991fc271 100644
--- a/pkg/gui/controllers/files_controller.go
+++ b/pkg/gui/controllers/files_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"strings"
 
 	"github.com/jesseduffield/gocui"
@@ -388,7 +389,7 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
 		// if any files within have inline merge conflicts we can't stage or unstage,
 		// or it'll end up with those >>>>>> lines actually staged
 		if node.GetHasInlineMergeConflicts() {
-			return self.c.ErrorMsg(self.c.Tr.ErrStageDirWithInlineMergeConflicts)
+			return errors.New(self.c.Tr.ErrStageDirWithInlineMergeConflicts)
 		}
 	}
 
@@ -410,7 +411,7 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
 		}
 
 		if err := self.c.Git().WorkingTree.StageFiles(toPaths(selectedNodes)); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 	} else {
 		self.c.LogAction(self.c.Tr.Actions.UnstageFile)
@@ -428,13 +429,13 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
 
 		if len(untrackedNodes) > 0 {
 			if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(toPaths(untrackedNodes)); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 		}
 
 		if len(trackedNodes) > 0 {
 			if err := self.c.Git().WorkingTree.UnstageTrackedFiles(toPaths(trackedNodes)); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 		}
 	}
@@ -496,7 +497,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
 		return self.switchToMerge()
 	}
 	if file.HasMergeConflicts {
-		return self.c.ErrorMsg(self.c.Tr.FileStagingRequirements)
+		return errors.New(self.c.Tr.FileStagingRequirements)
 	}
 
 	return self.c.PushContext(self.c.Contexts().Staging, opts)
@@ -523,7 +524,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
 	// if any files within have inline merge conflicts we can't stage or unstage,
 	// or it'll end up with those >>>>>> lines actually staged
 	if root.GetHasInlineMergeConflicts() {
-		return self.c.ErrorMsg(self.c.Tr.ErrStageDirWithInlineMergeConflicts)
+		return errors.New(self.c.Tr.ErrStageDirWithInlineMergeConflicts)
 	}
 
 	if root.GetHasUnstagedChanges() {
@@ -534,7 +535,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
 		}
 
 		if err := self.c.Git().WorkingTree.StageAll(); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 	} else {
 		self.c.LogAction(self.c.Tr.Actions.UnstageAllFiles)
@@ -544,7 +545,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
 		}
 
 		if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 	}
 
@@ -606,14 +607,14 @@ func (self *FilesController) ignoreOrExcludeFile(node *filetree.FileNode, trText
 
 func (self *FilesController) ignore(node *filetree.FileNode) error {
 	if node.GetPath() == ".gitignore" {
-		return self.c.ErrorMsg(self.c.Tr.Actions.IgnoreFileErr)
+		return errors.New(self.c.Tr.Actions.IgnoreFileErr)
 	}
 	return self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.IgnoreExcludeFile, self.c.Git().WorkingTree.Ignore)
 }
 
 func (self *FilesController) exclude(node *filetree.FileNode) error {
 	if node.GetPath() == ".gitignore" {
-		return self.c.ErrorMsg(self.c.Tr.Actions.ExcludeGitIgnoreErr)
+		return errors.New(self.c.Tr.Actions.ExcludeGitIgnoreErr)
 	}
 
 	return self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude)
@@ -627,7 +628,7 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
 				LabelColumns: []string{self.c.Tr.IgnoreFile},
 				OnPress: func() error {
 					if err := self.ignore(node); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return nil
 				},
@@ -637,7 +638,7 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
 				LabelColumns: []string{self.c.Tr.ExcludeFile},
 				OnPress: func() error {
 					if err := self.exclude(node); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return nil
 				},
@@ -658,7 +659,7 @@ func (self *FilesController) handleAmendCommitPress() error {
 		HandleConfirm: func() error {
 			return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
 				if len(self.c.Model().Commits) == 0 {
-					return self.c.ErrorMsg(self.c.Tr.NoCommitToAmend)
+					return errors.New(self.c.Tr.NoCommitToAmend)
 				}
 
 				return self.c.Helpers().AmendHelper.AmendHead()
@@ -765,7 +766,7 @@ func (self *FilesController) createStashMenu() error {
 				Label: self.c.Tr.StashAllChanges,
 				OnPress: func() error {
 					if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
-						return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
+						return errors.New(self.c.Tr.NoFilesToStash)
 					}
 					return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
 				},
@@ -775,7 +776,7 @@ func (self *FilesController) createStashMenu() error {
 				Label: self.c.Tr.StashAllChangesKeepIndex,
 				OnPress: func() error {
 					if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
-						return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
+						return errors.New(self.c.Tr.NoFilesToStash)
 					}
 					// if there are no staged files it behaves the same as Stash.Save
 					return self.handleStashSave(self.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
@@ -794,7 +795,7 @@ func (self *FilesController) createStashMenu() error {
 				OnPress: func() error {
 					// there must be something in staging otherwise the current implementation mucks the stash up
 					if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
-						return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
+						return errors.New(self.c.Tr.NoTrackedStagedFilesStash)
 					}
 					return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
 				},
@@ -804,7 +805,7 @@ func (self *FilesController) createStashMenu() error {
 				Label: self.c.Tr.StashUnstagedChanges,
 				OnPress: func() error {
 					if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
-						return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
+						return errors.New(self.c.Tr.NoFilesToStash)
 					}
 					if self.c.Helpers().WorkingTree.AnyStagedFiles() {
 						return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
@@ -825,7 +826,7 @@ func (self *FilesController) openCopyMenu() error {
 		Label: self.c.Tr.CopyFileName,
 		OnPress: func() error {
 			if err := self.c.OS().CopyToClipboard(node.Name()); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			self.c.Toast(self.c.Tr.FileNameCopiedToast)
 			return nil
@@ -837,7 +838,7 @@ func (self *FilesController) openCopyMenu() error {
 		Label: self.c.Tr.CopyFilePath,
 		OnPress: func() error {
 			if err := self.c.OS().CopyToClipboard(node.Path); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			self.c.Toast(self.c.Tr.FilePathCopiedToast)
 			return nil
@@ -853,10 +854,10 @@ func (self *FilesController) openCopyMenu() error {
 			hasStaged := self.hasPathStagedChanges(node)
 			diff, err := self.c.Git().Diff.GetPathDiff(path, hasStaged)
 			if err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			if err := self.c.OS().CopyToClipboard(diff); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			self.c.Toast(self.c.Tr.FileDiffCopiedToast)
 			return nil
@@ -878,10 +879,10 @@ func (self *FilesController) openCopyMenu() error {
 			hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles()
 			diff, err := self.c.Git().Diff.GetAllDiff(hasStaged)
 			if err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			if err := self.c.OS().CopyToClipboard(diff); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast)
 			return nil
@@ -957,7 +958,7 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
 			self.c.LogAction(action)
 
 			if err := stashFunc(stashComment); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
 		},
@@ -975,7 +976,7 @@ func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) e
 func (self *FilesController) fetch() error {
 	return self.c.WithWaitingStatus(self.c.Tr.FetchingStatus, func(task gocui.Task) error {
 		if err := self.fetchAux(task); err != nil {
-			_ = self.c.Error(err)
+			return err
 		}
 		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
 	})
@@ -986,7 +987,7 @@ func (self *FilesController) fetchAux(task gocui.Task) (err error) {
 	err = self.c.Git().Sync.Fetch(task)
 
 	if err != nil && strings.Contains(err.Error(), "exit status 128") {
-		_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
+		return errors.New(self.c.Tr.PassUnameWrong)
 	}
 
 	_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
@@ -1073,7 +1074,7 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
 
 				for _, node := range selectedNodes {
 					if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 				}
 
@@ -1101,7 +1102,7 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
 
 				for _, node := range selectedNodes {
 					if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 				}
 
@@ -1127,15 +1128,15 @@ func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) e
 		file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
 		if file != nil {
 			if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 		}
 
 		if err := self.c.Git().Submodule.Stash(submodule); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		if err := self.c.Git().Submodule.Reset(submodule); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 
 		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
diff --git a/pkg/gui/controllers/filtering_menu_action.go b/pkg/gui/controllers/filtering_menu_action.go
index 3771cdabde3..a340c3e9ac8 100644
--- a/pkg/gui/controllers/filtering_menu_action.go
+++ b/pkg/gui/controllers/filtering_menu_action.go
@@ -120,8 +120,9 @@ func (self *FilteringMenuAction) setFiltering() error {
 		return err
 	}
 
-	return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
+	return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() error {
 		self.c.Contexts().LocalCommits.SetSelection(0)
 		self.c.Contexts().LocalCommits.FocusLine()
+		return nil
 	}})
 }
diff --git a/pkg/gui/controllers/git_flow_controller.go b/pkg/gui/controllers/git_flow_controller.go
index 45ce0a5d0e0..7c3e45603cf 100644
--- a/pkg/gui/controllers/git_flow_controller.go
+++ b/pkg/gui/controllers/git_flow_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -46,7 +47,7 @@ func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*typ
 
 func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) error {
 	if !self.c.Git().Flow.GitFlowEnabled() {
-		return self.c.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
+		return errors.New("You need to install git-flow and enable it in this repo to use git-flow features")
 	}
 
 	startHandler := func(branchType string) func() error {
@@ -103,7 +104,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
 func (self *GitFlowController) gitFlowFinishBranch(branchName string) error {
 	cmdObj, err := self.c.Git().Flow.FinishCmdObj(branchName)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.LogAction(self.c.Tr.Actions.GitFlowFinish)
diff --git a/pkg/gui/controllers/helpers/app_status_helper.go b/pkg/gui/controllers/helpers/app_status_helper.go
index c0afcbbce94..b0d1b381836 100644
--- a/pkg/gui/controllers/helpers/app_status_helper.go
+++ b/pkg/gui/controllers/helpers/app_status_helper.go
@@ -59,26 +59,20 @@ func (self appStatusHelperTask) Continue() {
 
 // withWaitingStatus wraps a function and shows a waiting status while the function is still executing
 func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task) error) {
-	self.c.OnWorker(func(task gocui.Task) {
-		self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) {
-			if err := f(appStatusHelperTask{task, waitingStatusHandle}); err != nil {
-				self.c.OnUIThread(func() error {
-					return self.c.Error(err)
-				})
-			}
+	self.c.OnWorker(func(task gocui.Task) error {
+		return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error {
+			return f(appStatusHelperTask{task, waitingStatusHandle})
 		})
 	})
 }
 
-func (self *AppStatusHelper) WithWaitingStatusSync(message string, f func() error) {
-	self.statusMgr().WithWaitingStatus(message, func() {}, func(*status.WaitingStatusHandle) {
+func (self *AppStatusHelper) WithWaitingStatusSync(message string, f func() error) error {
+	return self.statusMgr().WithWaitingStatus(message, func() {}, func(*status.WaitingStatusHandle) error {
 		stop := make(chan struct{})
 		defer func() { close(stop) }()
 		self.renderAppStatusSync(stop)
 
-		if err := f(); err != nil {
-			_ = self.c.Error(err)
-		}
+		return f()
 	})
 }
 
@@ -92,7 +86,7 @@ func (self *AppStatusHelper) GetStatusString() string {
 }
 
 func (self *AppStatusHelper) renderAppStatus() {
-	self.c.OnWorker(func(_ gocui.Task) {
+	self.c.OnWorker(func(_ gocui.Task) error {
 		ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig.Gui.Spinner.Rate))
 		defer ticker.Stop()
 		for range ticker.C {
@@ -104,9 +98,10 @@ func (self *AppStatusHelper) renderAppStatus() {
 			})
 
 			if appStatus == "" {
-				return
+				break
 			}
 		}
+		return nil
 	})
 }
 
diff --git a/pkg/gui/controllers/helpers/bisect_helper.go b/pkg/gui/controllers/helpers/bisect_helper.go
index 7d0fadf8ad9..aa8bb50230a 100644
--- a/pkg/gui/controllers/helpers/bisect_helper.go
+++ b/pkg/gui/controllers/helpers/bisect_helper.go
@@ -19,7 +19,7 @@ func (self *BisectHelper) Reset() error {
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.ResetBisect)
 			if err := self.c.Git().Bisect.Reset(); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return self.PostBisectCommandRefresh()
diff --git a/pkg/gui/controllers/helpers/branches_helper.go b/pkg/gui/controllers/helpers/branches_helper.go
index 6bc336e8e76..d9d6dbd9ae5 100644
--- a/pkg/gui/controllers/helpers/branches_helper.go
+++ b/pkg/gui/controllers/helpers/branches_helper.go
@@ -37,7 +37,7 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName st
 			return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
 				self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
 				if err := self.c.Git().Remote.DeleteRemoteBranch(task, remoteName, branchName); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 				return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
 			})
diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go
index 8f8904808bc..6e1a181c77d 100644
--- a/pkg/gui/controllers/helpers/commits_helper.go
+++ b/pkg/gui/controllers/helpers/commits_helper.go
@@ -1,6 +1,7 @@
 package helpers
 
 import (
+	"errors"
 	"path/filepath"
 	"strings"
 	"time"
@@ -167,7 +168,7 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
 	summary, description := self.getCommitSummary(), self.getCommitDescription()
 
 	if summary == "" {
-		return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
+		return errors.New(self.c.Tr.CommitWithoutMessageErr)
 	}
 
 	err := self.c.Contexts().CommitMessage.OnConfirm(summary, description)
diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go
index 6cf5a1e9015..8c1265c15cf 100644
--- a/pkg/gui/controllers/helpers/confirmation_helper.go
+++ b/pkg/gui/controllers/helpers/confirmation_helper.go
@@ -36,7 +36,7 @@ func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.Can
 
 		if function != nil {
 			if err := function(); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 		}
 
diff --git a/pkg/gui/controllers/helpers/files_helper.go b/pkg/gui/controllers/helpers/files_helper.go
index dded9877fee..328d0f8895a 100644
--- a/pkg/gui/controllers/helpers/files_helper.go
+++ b/pkg/gui/controllers/helpers/files_helper.go
@@ -85,7 +85,7 @@ func (self *FilesHelper) OpenFile(filename string) error {
 	}
 	self.c.LogAction(self.c.Tr.Actions.OpenFile)
 	if err := self.c.OS().OpenFile(absPath); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	return nil
 }
diff --git a/pkg/gui/controllers/helpers/fixup_helper.go b/pkg/gui/controllers/helpers/fixup_helper.go
index 79104120e55..b60d48f4f92 100644
--- a/pkg/gui/controllers/helpers/fixup_helper.go
+++ b/pkg/gui/controllers/helpers/fixup_helper.go
@@ -1,6 +1,8 @@
 package helpers
 
 import (
+	"errors"
+	"fmt"
 	"regexp"
 	"strings"
 	"sync"
@@ -36,19 +38,19 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
 		return err
 	}
 	if diff == "" {
-		return self.c.ErrorMsg(self.c.Tr.NoChangedFiles)
+		return errors.New(self.c.Tr.NoChangedFiles)
 	}
 
 	deletedLineInfos, hasHunksWithOnlyAddedLines := self.parseDiff(diff)
 	if len(deletedLineInfos) == 0 {
-		return self.c.ErrorMsg(self.c.Tr.NoDeletedLinesInDiff)
+		return errors.New(self.c.Tr.NoDeletedLinesInDiff)
 	}
 
 	hashes := self.blameDeletedLines(deletedLineInfos)
 
 	if len(hashes) == 0 {
 		// This should never happen
-		return self.c.ErrorMsg(self.c.Tr.NoBaseCommitsFound)
+		return errors.New(self.c.Tr.NoBaseCommitsFound)
 	}
 	if len(hashes) > 1 {
 		subjects, err := self.c.Git().Commit.GetHashesAndCommitMessagesFirstLine(hashes)
@@ -58,7 +60,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
 		message := lo.Ternary(hasStagedChanges,
 			self.c.Tr.MultipleBaseCommitsFoundStaged,
 			self.c.Tr.MultipleBaseCommitsFoundUnstaged)
-		return self.c.ErrorMsg(message + "\n\n" + subjects)
+		return fmt.Errorf("%s\n\n%s", message, subjects)
 	}
 
 	commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
@@ -70,13 +72,13 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
 			// If the commit is not found, it's most likely because it's already
 			// merged, and more than 300 commits away. Check if the last known
 			// commit is already merged; if so, show the "already merged" error.
-			return self.c.ErrorMsg(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
+			return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
 		}
 		// If we get here, the current branch must have more then 300 commits. Unlikely...
-		return self.c.ErrorMsg(self.c.Tr.BaseCommitIsNotInCurrentView)
+		return errors.New(self.c.Tr.BaseCommitIsNotInCurrentView)
 	}
 	if commit.Status == models.StatusMerged {
-		return self.c.ErrorMsg(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
+		return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
 	}
 
 	doIt := func() error {
diff --git a/pkg/gui/controllers/helpers/gpg_helper.go b/pkg/gui/controllers/helpers/gpg_helper.go
index df440104b89..a49c0cb3896 100644
--- a/pkg/gui/controllers/helpers/gpg_helper.go
+++ b/pkg/gui/controllers/helpers/gpg_helper.go
@@ -45,10 +45,8 @@ func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus str
 	return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
 		if err := cmdObj.StreamOutput().Run(); err != nil {
 			_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
-			return self.c.Error(
-				fmt.Errorf(
-					self.c.Tr.GitCommandFailed, self.c.UserConfig.Keybinding.Universal.ExtrasMenu,
-				),
+			return fmt.Errorf(
+				self.c.Tr.GitCommandFailed, self.c.UserConfig.Keybinding.Universal.ExtrasMenu,
 			)
 		}
 
diff --git a/pkg/gui/controllers/helpers/inline_status_helper.go b/pkg/gui/controllers/helpers/inline_status_helper.go
index bcf186a3193..b13f19473f0 100644
--- a/pkg/gui/controllers/helpers/inline_status_helper.go
+++ b/pkg/gui/controllers/helpers/inline_status_helper.go
@@ -68,17 +68,11 @@ func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(g
 	view := context.GetView()
 	visible := view.Visible && self.windowHelper.TopViewInWindow(context.GetWindowName(), false) == view
 	if visible && context.IsItemVisible(opts.Item) {
-		self.c.OnWorker(func(task gocui.Task) {
+		self.c.OnWorker(func(task gocui.Task) error {
 			self.start(opts)
+			defer self.stop(opts)
 
-			err := f(inlineStatusHelperTask{task, self, opts})
-			if err != nil {
-				self.c.OnUIThread(func() error {
-					return self.c.Error(err)
-				})
-			}
-
-			self.stop(opts)
+			return f(inlineStatusHelperTask{task, self, opts})
 		})
 	} else {
 		message := presentation.ItemOperationToString(opts.Operation, self.c.Tr)
diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
index 16b1eea2b08..4bffcfa99ee 100644
--- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
+++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
@@ -1,6 +1,7 @@
 package helpers
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -78,7 +79,7 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
 	status := self.c.Git().Status.WorkingTreeState()
 
 	if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
-		return self.c.ErrorMsg(self.c.Tr.NotMergingOrRebasing)
+		return errors.New(self.c.Tr.NotMergingOrRebasing)
 	}
 
 	self.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
@@ -169,9 +170,9 @@ func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error {
 
 	if isMergeConflictErr(result.Error()) {
 		return self.PromptForConflictHandling()
-	} else {
-		return self.c.ErrorMsg(result.Error())
 	}
+
+	return result
 }
 
 func (self *MergeAndRebaseHelper) PromptForConflictHandling() error {
@@ -298,11 +299,11 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
 
 func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) error {
 	if self.c.Git().Branch.IsHeadDetached() {
-		return self.c.ErrorMsg("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
+		return errors.New("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
 	}
 	checkedOutBranchName := self.refsHelper.GetCheckedOutRef().Name
 	if checkedOutBranchName == refName {
-		return self.c.ErrorMsg(self.c.Tr.CantMergeBranchIntoItself)
+		return errors.New(self.c.Tr.CantMergeBranchIntoItself)
 	}
 	prompt := utils.ResolvePlaceholderString(
 		self.c.Tr.ConfirmMerge,
diff --git a/pkg/gui/controllers/helpers/merge_conflicts_helper.go b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
index 31f7bd5e3d2..249be4b69a0 100644
--- a/pkg/gui/controllers/helpers/merge_conflicts_helper.go
+++ b/pkg/gui/controllers/helpers/merge_conflicts_helper.go
@@ -129,7 +129,7 @@ func (self *MergeConflictsHelper) RefreshMergeState() error {
 
 	hasConflicts, err := self.SetConflictsAndRender(self.c.Contexts().MergeConflicts.GetState().GetPath())
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	if !hasConflicts {
diff --git a/pkg/gui/controllers/helpers/mode_helper.go b/pkg/gui/controllers/helpers/mode_helper.go
index 0745c2288da..5798621552f 100644
--- a/pkg/gui/controllers/helpers/mode_helper.go
+++ b/pkg/gui/controllers/helpers/mode_helper.go
@@ -171,7 +171,7 @@ func (self *ModeHelper) ClearFiltering() error {
 
 	return self.c.Refresh(types.RefreshOptions{
 		Scope: []types.RefreshableView{types.COMMITS},
-		Then: func() {
+		Then: func() error {
 			// Find the commit that was last selected in filtering mode, and select it again after refreshing
 			if !self.c.Contexts().LocalCommits.SelectCommitByHash(selectedCommitHash) {
 				// If we couldn't find it (either because no commit was selected
@@ -180,6 +180,7 @@ func (self *ModeHelper) ClearFiltering() error {
 				// before we entered filtering
 				self.c.Contexts().LocalCommits.SelectCommitByHash(self.c.Modes().Filtering.GetSelectedCommitHash())
 			}
+			return nil
 		},
 	})
 }
diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go
index a9fe4f6da77..d8f83255d7a 100644
--- a/pkg/gui/controllers/helpers/patch_building_helper.go
+++ b/pkg/gui/controllers/helpers/patch_building_helper.go
@@ -1,6 +1,8 @@
 package helpers
 
 import (
+	"errors"
+
 	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -24,7 +26,7 @@ func NewPatchBuildingHelper(
 
 func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error) {
 	if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
-		return false, self.c.ErrorMsg(self.c.Tr.CantPatchWhileRebasingError)
+		return false, errors.New(self.c.Tr.CantPatchWhileRebasingError)
 	}
 	return true, nil
 }
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index 4e1a9fc543a..8d88faeec2a 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -76,7 +76,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
 		)
 	}
 
-	f := func() {
+	f := func() error {
 		var scopeSet *set.Set[types.RefreshableView]
 		if len(options.Scope) == 0 {
 			// not refreshing staging/patch-building unless explicitly requested because we only need
@@ -104,8 +104,9 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
 			// everything happens fast and it's better to have everything update
 			// in the one frame
 			if !self.c.InDemo() && options.Mode == types.ASYNC {
-				self.c.OnWorker(func(t gocui.Task) {
+				self.c.OnWorker(func(t gocui.Task) error {
 					f()
+					return nil
 				})
 			} else {
 				wg.Add(1)
@@ -187,20 +188,22 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
 		wg.Wait()
 
 		if options.Then != nil {
-			options.Then()
+			if err := options.Then(); err != nil {
+				return err
+			}
 		}
+
+		return nil
 	}
 
 	if options.Mode == types.BLOCK_UI {
 		self.c.OnUIThread(func() error {
-			f()
-			return nil
+			return f()
 		})
-	} else {
-		f()
+		return nil
 	}
 
-	return nil
+	return f()
 }
 
 func getScopeNames(scopes []types.RefreshableView) []string {
@@ -246,10 +249,11 @@ func getModeName(mode types.RefreshMode) string {
 func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
 	switch self.c.State().GetRepoState().GetStartupStage() {
 	case types.INITIAL:
-		self.c.OnWorker(func(_ gocui.Task) {
+		self.c.OnWorker(func(_ gocui.Task) error {
 			_ = self.refreshReflogCommits()
 			self.refreshBranches(false, true)
 			self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
+			return nil
 		})
 
 	case types.COMPLETE:
@@ -381,7 +385,7 @@ func (self *RefreshHelper) refreshCommitFilesContext() error {
 
 	files, err := self.c.Git().Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	self.c.Model().CommitFiles = files
 	self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
@@ -406,7 +410,7 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
 func (self *RefreshHelper) refreshTags() error {
 	tags, err := self.c.Git().Loaders.TagLoader.GetTags()
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Model().Tags = tags
@@ -448,7 +452,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
 
 	branches, err := self.c.Git().Loaders.BranchLoader.Load(reflogCommits)
 	if err != nil {
-		_ = self.c.Error(err)
+		self.c.Log.Error(err)
 	}
 
 	self.c.Model().Branches = branches
@@ -543,7 +547,7 @@ func (self *RefreshHelper) refreshStateFiles() error {
 	if len(pathsToStage) > 0 {
 		self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles)
 		if err := self.c.Git().WorkingTree.StageFiles(pathsToStage); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 	}
 
@@ -603,7 +607,7 @@ func (self *RefreshHelper) refreshReflogCommits() error {
 		commits, onlyObtainedNewReflogCommits, err := self.c.Git().Loaders.ReflogCommitLoader.
 			GetReflogCommits(lastReflogCommit, filterPath, filterAuthor)
 		if err != nil {
-			return self.c.Error(err)
+			return err
 		}
 
 		if onlyObtainedNewReflogCommits {
@@ -634,7 +638,7 @@ func (self *RefreshHelper) refreshRemotes() error {
 
 	remotes, err := self.c.Git().Loaders.RemoteLoader.GetRemotes()
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Model().Remotes = remotes
diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go
index 6ddff118c5a..d837d826647 100644
--- a/pkg/gui/controllers/helpers/refs_helper.go
+++ b/pkg/gui/controllers/helpers/refs_helper.go
@@ -69,10 +69,10 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
 					Prompt: self.c.Tr.AutoStashPrompt,
 					HandleConfirm: func() error {
 						if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + ref); err != nil {
-							return self.c.Error(err)
+							return err
 						}
 						if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
-							return self.c.Error(err)
+							return err
 						}
 
 						onSuccess()
@@ -80,16 +80,14 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
 							if err := self.c.Refresh(refreshOptions); err != nil {
 								return err
 							}
-							return self.c.Error(err)
+							return err
 						}
 						return self.c.Refresh(refreshOptions)
 					},
 				})
 			}
 
-			if err := self.c.Error(err); err != nil {
-				return err
-			}
+			return err
 		}
 		onSuccess()
 
@@ -142,7 +140,7 @@ func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchN
 					// "git checkout -b", but we want to benefit from all the
 					// nice features of the CheckoutRef function.
 					if err := self.c.Git().Branch.CreateWithUpstream(localBranchName, fullBranchName); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					// Do a sync refresh to make sure the new branch is visible,
 					// so that we see an inline status when checking it out
@@ -176,7 +174,7 @@ func (self *RefsHelper) GetCheckedOutRef() *models.Branch {
 
 func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string) error {
 	if err := self.c.Git().Commit.ResetToCommit(ref, strength, envVars); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	self.c.Contexts().LocalCommits.SetSelection(0)
diff --git a/pkg/gui/controllers/helpers/repos_helper.go b/pkg/gui/controllers/helpers/repos_helper.go
index 22883510a53..decebba3699 100644
--- a/pkg/gui/controllers/helpers/repos_helper.go
+++ b/pkg/gui/controllers/helpers/repos_helper.go
@@ -1,6 +1,7 @@
 package helpers
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -156,7 +157,7 @@ func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey
 
 		if err := os.Chdir(path); err != nil {
 			if os.IsNotExist(err) {
-				return self.c.ErrorMsg(errMsg)
+				return errors.New(errMsg)
 			}
 			return err
 		}
diff --git a/pkg/gui/controllers/helpers/snake_helper.go b/pkg/gui/controllers/helpers/snake_helper.go
index fb4d67bb4c5..6940bbd0296 100644
--- a/pkg/gui/controllers/helpers/snake_helper.go
+++ b/pkg/gui/controllers/helpers/snake_helper.go
@@ -1,6 +1,7 @@
 package helpers
 
 import (
+	"errors"
 	"fmt"
 	"strings"
 
@@ -39,7 +40,7 @@ func (self *SnakeHelper) renderSnakeGame(cells [][]snake.CellType, alive bool) {
 	view := self.c.Views().Snake
 
 	if !alive {
-		_ = self.c.ErrorMsg(self.c.Tr.YouDied)
+		self.c.OnUIThread(func() error { return errors.New(self.c.Tr.YouDied) })
 		return
 	}
 
diff --git a/pkg/gui/controllers/helpers/tags_helper.go b/pkg/gui/controllers/helpers/tags_helper.go
index 51957355a5b..542037fba27 100644
--- a/pkg/gui/controllers/helpers/tags_helper.go
+++ b/pkg/gui/controllers/helpers/tags_helper.go
@@ -25,12 +25,12 @@ func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
 			if description != "" {
 				self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
 				if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, description, force); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 			} else {
 				self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
 				if err := self.c.Git().Tag.CreateLightweight(tagName, ref, force); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 			}
 
diff --git a/pkg/gui/controllers/helpers/update_helper.go b/pkg/gui/controllers/helpers/update_helper.go
index 36cb6558a0f..f50a757deb4 100644
--- a/pkg/gui/controllers/helpers/update_helper.go
+++ b/pkg/gui/controllers/helpers/update_helper.go
@@ -1,6 +1,8 @@
 package helpers
 
 import (
+	"errors"
+
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/jesseduffield/lazygit/pkg/updates"
@@ -41,10 +43,10 @@ func (self *UpdateHelper) CheckForUpdateInForeground() error {
 	return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func(gocui.Task) error {
 		self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
 			if err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			if newVersion == "" {
-				return self.c.ErrorMsg(self.c.Tr.FailedToRetrieveLatestVersionErr)
+				return errors.New(self.c.Tr.FailedToRetrieveLatestVersionErr)
 			}
 			return self.showUpdatePrompt(newVersion)
 		}, true)
@@ -71,7 +73,7 @@ func (self *UpdateHelper) onUpdateFinish(err error) error {
 					"errMessage": err.Error(),
 				},
 			)
-			return self.c.ErrorMsg(errMessage)
+			return errors.New(errMessage)
 		}
 		return self.c.Alert(self.c.Tr.UpdateCompletedTitle, self.c.Tr.UpdateCompleted)
 	})
diff --git a/pkg/gui/controllers/helpers/working_tree_helper.go b/pkg/gui/controllers/helpers/working_tree_helper.go
index 6f00c747990..edd7005e9cc 100644
--- a/pkg/gui/controllers/helpers/working_tree_helper.go
+++ b/pkg/gui/controllers/helpers/working_tree_helper.go
@@ -1,6 +1,7 @@
 package helpers
 
 import (
+	"errors"
 	"fmt"
 	"regexp"
 
@@ -137,7 +138,7 @@ func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
 func (self *WorkingTreeHelper) HandleWIPCommitPress() error {
 	skipHookPrefix := self.c.UserConfig.Git.SkipHookPrefix
 	if skipHookPrefix == "" {
-		return self.c.ErrorMsg(self.c.Tr.SkipHookPrefixNotConfigured)
+		return errors.New(self.c.Tr.SkipHookPrefixNotConfigured)
 	}
 
 	return self.HandleCommitPressWithMessage(skipHookPrefix)
@@ -153,7 +154,7 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
 			prefixReplace := commitPrefixConfig.Replace
 			rgx, err := regexp.Compile(prefixPattern)
 			if err != nil {
-				return self.c.ErrorMsg(fmt.Sprintf("%s: %s", self.c.Tr.CommitPrefixPatternError, err.Error()))
+				return fmt.Errorf("%s: %s", self.c.Tr.CommitPrefixPatternError, err.Error())
 			}
 			prefix := rgx.ReplaceAllString(self.refHelper.GetCheckedOutRef().Name, prefixReplace)
 			message = prefix
@@ -165,11 +166,11 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
 
 func (self *WorkingTreeHelper) WithEnsureCommitableFiles(handler func() error) error {
 	if err := self.prepareFilesForCommit(); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	if len(self.c.Model().Files) == 0 {
-		return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
+		return errors.New(self.c.Tr.NoFilesStagedTitle)
 	}
 
 	if !self.AnyStagedFiles() {
@@ -186,10 +187,10 @@ func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) erro
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
 			if err := self.c.Git().WorkingTree.StageAll(); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			if err := self.syncRefresh(); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return retry()
diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go
index 8126f3d8e95..7c763e0fb61 100644
--- a/pkg/gui/controllers/helpers/worktree_helper.go
+++ b/pkg/gui/controllers/helpers/worktree_helper.go
@@ -1,6 +1,7 @@
 package helpers
 
 import (
+	"errors"
 	"strings"
 
 	"github.com/jesseduffield/gocui"
@@ -139,7 +140,7 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo
 					Title: self.c.Tr.NewBranchName,
 					HandleConfirm: func(branchName string) error {
 						if branchName == "" {
-							return self.c.ErrorMsg(self.c.Tr.BranchNameCannotBeBlank)
+							return errors.New(self.c.Tr.BranchNameCannotBeBlank)
 						}
 
 						opts.Branch = branchName
@@ -154,7 +155,7 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo
 
 func (self *WorktreeHelper) Switch(worktree *models.Worktree, contextKey types.ContextKey) error {
 	if worktree.IsCurrent {
-		return self.c.ErrorMsg(self.c.Tr.AlreadyInWorktree)
+		return errors.New(self.c.Tr.AlreadyInWorktree)
 	}
 
 	self.c.LogAction(self.c.Tr.SwitchToWorktree)
@@ -186,13 +187,13 @@ func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error
 				if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil {
 					errMessage := err.Error()
 					if !strings.Contains(errMessage, "--force") {
-						return self.c.Error(err)
+						return err
 					}
 
 					if !force {
 						return self.Remove(worktree, true)
 					}
-					return self.c.ErrorMsg(errMessage)
+					return err
 				}
 				return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
 			})
@@ -206,7 +207,7 @@ func (self *WorktreeHelper) Detach(worktree *models.Worktree) error {
 
 		err := self.c.Git().Worktree.Detach(worktree.Path)
 		if err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
 	})
diff --git a/pkg/gui/controllers/list_controller_trait.go b/pkg/gui/controllers/list_controller_trait.go
index 0edaa0114ed..fa5fc1492f4 100644
--- a/pkg/gui/controllers/list_controller_trait.go
+++ b/pkg/gui/controllers/list_controller_trait.go
@@ -1,6 +1,10 @@
 package controllers
 
-import "github.com/jesseduffield/lazygit/pkg/gui/types"
+import (
+	"errors"
+
+	"github.com/jesseduffield/lazygit/pkg/gui/types"
+)
 
 // Embed this into your list controller to get some convenience methods for
 // ensuring a single item is selected, etc.
@@ -106,7 +110,7 @@ func (self *ListControllerTrait[T]) withItem(callback func(T) error) func() erro
 		var zeroValue T
 		commit := self.getSelectedItem()
 		if commit == zeroValue {
-			return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
+			return errors.New(self.c.Tr.NoItemSelected)
 		}
 
 		return callback(commit)
@@ -117,7 +121,7 @@ func (self *ListControllerTrait[T]) withItems(callback func([]T) error) func() e
 	return func() error {
 		items, _, _ := self.getSelectedItems()
 		if len(items) == 0 {
-			return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
+			return errors.New(self.c.Tr.NoItemSelected)
 		}
 
 		return callback(items)
@@ -129,7 +133,7 @@ func (self *ListControllerTrait[T]) withItemsRange(callback func([]T, int, int)
 	return func() error {
 		items, startIdx, endIdx := self.getSelectedItems()
 		if len(items) == 0 {
-			return self.c.ErrorMsg(self.c.Tr.NoItemSelected)
+			return errors.New(self.c.Tr.NoItemSelected)
 		}
 
 		return callback(items, startIdx, endIdx)
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index 3f301b3ea47..1336f523758 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -352,7 +352,7 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
 func (self *LocalCommitsController) reword(commit *models.Commit) error {
 	commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
 		commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
@@ -399,7 +399,7 @@ func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepat
 func (self *LocalCommitsController) handleReword(summary string, description string) error {
 	err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	self.c.Helpers().Commits.OnCommitSuccess()
 	return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -416,7 +416,7 @@ func (self *LocalCommitsController) doRewordEditor() error {
 		self.c.Model().Commits, self.context().GetSelectedLineIdx(),
 	)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	if subProcess != nil {
 		return self.c.RunSubprocessAndRefresh(subProcess)
@@ -495,7 +495,7 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit) error
 func (self *LocalCommitsController) quickStartInteractiveRebase() error {
 	commitToEdit, err := self.findCommitForQuickStartInteractiveRebase()
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return self.startInteractiveRebaseWithEdit([]*models.Commit{commitToEdit})
@@ -513,7 +513,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
 		err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash)
 		return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
 			err,
-			types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
+			types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() error {
 				todos := make([]*models.Commit, 0, len(commitsToEdit)-1)
 				for _, c := range commitsToEdit[:len(commitsToEdit)-1] {
 					// Merge commits can't be set to "edit", so just skip them
@@ -524,7 +524,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
 				if len(todos) > 0 {
 					err := self.updateTodos(todo.Edit, todos)
 					if err != nil {
-						_ = self.c.Error(err)
+						return err
 					}
 				}
 
@@ -540,6 +540,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
 				if ok1 && ok2 {
 					self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, rangeSelectMode)
 				}
+				return nil
 			}})
 	})
 }
@@ -587,7 +588,7 @@ func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, s
 // begin a rebase. It then updates the todo file with that action
 func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selectedCommits []*models.Commit) error {
 	if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return self.c.Refresh(types.RefreshOptions{
@@ -620,7 +621,7 @@ func (self *LocalCommitsController) isRebasing() bool {
 func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
 	if self.isRebasing() {
 		if err := self.c.Git().Rebase.MoveTodosDown(selectedCommits); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		self.context().MoveSelection(1)
 
@@ -643,7 +644,7 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
 func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
 	if self.isRebasing() {
 		if err := self.c.Git().Rebase.MoveTodosUp(selectedCommits); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		self.context().MoveSelection(-1)
 
@@ -733,7 +734,7 @@ func (self *LocalCommitsController) resetAuthor() error {
 	return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
 		self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
 		if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil {
-			return self.c.Error(err)
+			return err
 		}
 
 		return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -748,7 +749,7 @@ func (self *LocalCommitsController) setAuthor() error {
 			return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
 				self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
 				if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -765,7 +766,7 @@ func (self *LocalCommitsController) addCoAuthor() error {
 			return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
 				self.c.LogAction(self.c.Tr.Actions.AddCommitCoAuthor)
 				if err := self.c.Git().Rebase.AddCommitCoAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 				return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
 			})
@@ -803,7 +804,7 @@ func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.C
 		i := i
 		message, err := self.c.Git().Commit.GetCommitMessageFirstLine(parentHash)
 		if err != nil {
-			return self.c.Error(err)
+			return err
 		}
 
 		menuItems[i] = &types.MenuItem{
@@ -851,7 +852,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
 						self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
 						return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
 							if err := self.c.Git().Commit.CreateFixupCommit(commit.Hash); err != nil {
-								return self.c.Error(err)
+								return err
 							}
 
 							self.context().MoveSelectedLine(1)
@@ -886,7 +887,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
 func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error {
 	commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 	if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
 		commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
@@ -903,7 +904,7 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
 				self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
 				return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
 					if err := self.c.Git().Commit.CreateAmendCommit(originalSubject, summary, description, includeFileChanges); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 
 					self.context().MoveSelectedLine(1)
@@ -944,7 +945,7 @@ func (self *LocalCommitsController) squashAllFixupsAboveSelectedCommit(commit *m
 func (self *LocalCommitsController) squashAllFixupsInCurrentBranch() error {
 	commit, rebaseStartIdx, err := self.findCommitForSquashFixupsInCurrentBranch()
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return self.squashFixupsImpl(commit, rebaseStartIdx)
@@ -1153,10 +1154,8 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
 		context := self.context()
 		if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
 			context.SetLimitCommits(false)
-			self.c.OnWorker(func(_ gocui.Task) {
-				if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
-					_ = self.c.Error(err)
-				}
+			self.c.OnWorker(func(_ gocui.Task) error {
+				return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}})
 			})
 		}
 
diff --git a/pkg/gui/controllers/patch_explorer_controller.go b/pkg/gui/controllers/patch_explorer_controller.go
index 9d736df10d7..d75fe060d19 100644
--- a/pkg/gui/controllers/patch_explorer_controller.go
+++ b/pkg/gui/controllers/patch_explorer_controller.go
@@ -286,7 +286,7 @@ func (self *PatchExplorerController) CopySelectedToClipboard() error {
 
 	self.c.LogAction(self.c.Tr.Actions.CopySelectedTextToClipboard)
 	if err := self.c.OS().CopyToClipboard(selected); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return nil
diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go
index b46b5dbb1eb..97dbf56b00e 100644
--- a/pkg/gui/controllers/remote_branches_controller.go
+++ b/pkg/gui/controllers/remote_branches_controller.go
@@ -169,7 +169,7 @@ func (self *RemoteBranchesController) setAsUpstream(selectedBranch *models.Remot
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.SetBranchUpstream)
 			if err := self.c.Git().Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
diff --git a/pkg/gui/controllers/remotes_controller.go b/pkg/gui/controllers/remotes_controller.go
index f15bc1146b4..bf47a20cdc0 100644
--- a/pkg/gui/controllers/remotes_controller.go
+++ b/pkg/gui/controllers/remotes_controller.go
@@ -179,7 +179,7 @@ func (self *RemotesController) remove(remote *models.Remote) error {
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.RemoveRemote)
 			if err := self.c.Git().Remote.RemoveRemote(remote.Name); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
@@ -202,7 +202,7 @@ func (self *RemotesController) edit(remote *models.Remote) error {
 			if updatedRemoteName != remote.Name {
 				self.c.LogAction(self.c.Tr.Actions.UpdateRemote)
 				if err := self.c.Git().Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 			}
 
@@ -225,7 +225,7 @@ func (self *RemotesController) edit(remote *models.Remote) error {
 				HandleConfirm: func(updatedRemoteUrl string) error {
 					self.c.LogAction(self.c.Tr.Actions.UpdateRemote)
 					if err := self.c.Git().Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
 				},
@@ -238,7 +238,7 @@ func (self *RemotesController) fetch(remote *models.Remote) error {
 	return self.c.WithInlineStatus(remote, types.ItemOperationFetching, context.REMOTES_CONTEXT_KEY, func(task gocui.Task) error {
 		err := self.c.Git().Sync.FetchRemote(task, remote.Name)
 		if err != nil {
-			_ = self.c.Error(err)
+			return err
 		}
 
 		return self.c.Refresh(types.RefreshOptions{
diff --git a/pkg/gui/controllers/staging_controller.go b/pkg/gui/controllers/staging_controller.go
index c698ff68f46..8854aa2396b 100644
--- a/pkg/gui/controllers/staging_controller.go
+++ b/pkg/gui/controllers/staging_controller.go
@@ -244,7 +244,7 @@ func (self *StagingController) applySelection(reverse bool) error {
 		},
 	)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	if state.SelectingRange() {
@@ -317,7 +317,7 @@ func (self *StagingController) editHunk() error {
 			Cached:  true,
 		},
 	); err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return nil
diff --git a/pkg/gui/controllers/stash_controller.go b/pkg/gui/controllers/stash_controller.go
index 8b5e068b553..403c22b67a7 100644
--- a/pkg/gui/controllers/stash_controller.go
+++ b/pkg/gui/controllers/stash_controller.go
@@ -109,7 +109,7 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err
 		err := self.c.Git().Stash.Apply(stashEntry.Index)
 		_ = self.postStashRefresh()
 		if err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		return nil
 	}
@@ -133,7 +133,7 @@ func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error
 		err := self.c.Git().Stash.Pop(stashEntry.Index)
 		_ = self.postStashRefresh()
 		if err != nil {
-			return self.c.Error(err)
+			return err
 		}
 		return nil
 	}
@@ -160,7 +160,7 @@ func (self *StashController) handleStashDrop(stashEntry *models.StashEntry) erro
 			err := self.c.Git().Stash.Drop(stashEntry.Index)
 			_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
 			if err != nil {
-				return self.c.Error(err)
+				return err
 			}
 			return nil
 		},
diff --git a/pkg/gui/controllers/sub_commits_controller.go b/pkg/gui/controllers/sub_commits_controller.go
index 90c6461a6fe..9ce708272f7 100644
--- a/pkg/gui/controllers/sub_commits_controller.go
+++ b/pkg/gui/controllers/sub_commits_controller.go
@@ -68,10 +68,8 @@ func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
 		context := self.context()
 		if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
 			context.SetLimitCommits(false)
-			self.c.OnWorker(func(_ gocui.Task) {
-				if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil {
-					_ = self.c.Error(err)
-				}
+			self.c.OnWorker(func(_ gocui.Task) error {
+				return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}})
 			})
 		}
 
diff --git a/pkg/gui/controllers/submodules_controller.go b/pkg/gui/controllers/submodules_controller.go
index dde1a1f46d2..ba6db62503e 100644
--- a/pkg/gui/controllers/submodules_controller.go
+++ b/pkg/gui/controllers/submodules_controller.go
@@ -163,7 +163,7 @@ func (self *SubmodulesController) add() error {
 								self.c.LogAction(self.c.Tr.Actions.AddSubmodule)
 								err := self.c.Git().Submodule.Add(submoduleName, submodulePath, submoduleUrl)
 								if err != nil {
-									_ = self.c.Error(err)
+									return err
 								}
 
 								return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -185,7 +185,7 @@ func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) err
 				self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl)
 				err := self.c.Git().Submodule.UpdateUrl(submodule, newUrl)
 				if err != nil {
-					_ = self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -199,7 +199,7 @@ func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error
 		self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule)
 		err := self.c.Git().Submodule.Init(submodule.Path)
 		if err != nil {
-			_ = self.c.Error(err)
+			return err
 		}
 
 		return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -217,7 +217,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
 						self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules)
 						err := self.c.Git().Submodule.BulkInitCmdObj().Run()
 						if err != nil {
-							return self.c.Error(err)
+							return err
 						}
 
 						return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -231,7 +231,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
 					return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
 						self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules)
 						if err := self.c.Git().Submodule.BulkUpdateCmdObj().Run(); err != nil {
-							return self.c.Error(err)
+							return err
 						}
 
 						return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -245,7 +245,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
 					return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
 						self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules)
 						if err := self.c.Git().Submodule.BulkDeinitCmdObj().Run(); err != nil {
-							return self.c.Error(err)
+							return err
 						}
 
 						return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -262,7 +262,7 @@ func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) erro
 		self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule)
 		err := self.c.Git().Submodule.Update(submodule.Path)
 		if err != nil {
-			_ = self.c.Error(err)
+			return err
 		}
 
 		return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
@@ -276,7 +276,7 @@ func (self *SubmodulesController) remove(submodule *models.SubmoduleConfig) erro
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.RemoveSubmodule)
 			if err := self.c.Git().Submodule.Delete(submodule); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES, types.FILES}})
diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go
index 67fa7eac250..403f31d94e2 100644
--- a/pkg/gui/controllers/sync_controller.go
+++ b/pkg/gui/controllers/sync_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"fmt"
 	"strings"
 
@@ -101,7 +102,7 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
 			return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error {
 				upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
 				if err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.pushAux(currentBranch, pushOpts{
@@ -121,7 +122,7 @@ func (self *SyncController) pull(currentBranch *models.Branch) error {
 	if !currentBranch.IsTrackingRemote() {
 		return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error {
 			if err := self.setCurrentBranchUpstream(upstream); err != nil {
-				return self.c.Error(err)
+				return err
 			}
 
 			return self.PullAux(currentBranch, PullFilesOptions{Action: action})
@@ -197,7 +198,7 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
 			})
 		if err != nil {
 			if strings.Contains(err.Error(), "Updates were rejected") {
-				return self.c.ErrorMsg(self.c.Tr.UpdatesRejected)
+				return errors.New(self.c.Tr.UpdatesRejected)
 			}
 			return err
 		}
@@ -208,7 +209,7 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
 func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
 	forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
 	if forcePushDisabled {
-		return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
+		return errors.New(self.c.Tr.ForcePushDisabled)
 	}
 
 	return self.c.Confirm(types.ConfirmOpts{
diff --git a/pkg/gui/controllers/toggle_whitespace_action.go b/pkg/gui/controllers/toggle_whitespace_action.go
index 175738b59b6..41232a7697d 100644
--- a/pkg/gui/controllers/toggle_whitespace_action.go
+++ b/pkg/gui/controllers/toggle_whitespace_action.go
@@ -1,6 +1,8 @@
 package controllers
 
 import (
+	"errors"
+
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/samber/lo"
@@ -20,7 +22,7 @@ func (self *ToggleWhitespaceAction) Call() error {
 	if lo.Contains(contextsThatDontSupportIgnoringWhitespace, self.c.CurrentContext().GetKey()) {
 		// Ignoring whitespace is not supported in these views. Let the user
 		// know that it's not going to work in case they try to turn it on.
-		return self.c.ErrorMsg(self.c.Tr.IgnoreWhitespaceNotSupportedHere)
+		return errors.New(self.c.Tr.IgnoreWhitespaceNotSupportedHere)
 	}
 
 	self.c.GetAppState().IgnoreWhitespaceInDiffView = !self.c.GetAppState().IgnoreWhitespaceInDiffView
diff --git a/pkg/gui/controllers/undo_controller.go b/pkg/gui/controllers/undo_controller.go
index 2df3b8689f7..96e9f3f1161 100644
--- a/pkg/gui/controllers/undo_controller.go
+++ b/pkg/gui/controllers/undo_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/jesseduffield/gocui"
@@ -78,7 +79,7 @@ func (self *UndoController) reflogUndo() error {
 	undoingStatus := self.c.Tr.UndoingStatus
 
 	if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
-		return self.c.ErrorMsg(self.c.Tr.CantUndoWhileRebasing)
+		return errors.New(self.c.Tr.CantUndoWhileRebasing)
 	}
 
 	return self.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
@@ -126,7 +127,7 @@ func (self *UndoController) reflogRedo() error {
 	redoingStatus := self.c.Tr.RedoingStatus
 
 	if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
-		return self.c.ErrorMsg(self.c.Tr.CantRedoWhileRebasing)
+		return errors.New(self.c.Tr.CantRedoWhileRebasing)
 	}
 
 	return self.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
@@ -234,10 +235,7 @@ type hardResetOptions struct {
 // only to be used in the undo flow for now (does an autostash)
 func (self *UndoController) hardResetWithAutoStash(commitHash string, options hardResetOptions) error {
 	reset := func() error {
-		if err := self.c.Helpers().Refs.ResetToRef(commitHash, "hard", options.EnvVars); err != nil {
-			return self.c.Error(err)
-		}
-		return nil
+		return self.c.Helpers().Refs.ResetToRef(commitHash, "hard", options.EnvVars)
 	}
 
 	// if we have any modified tracked files we need to ask the user if they want us to stash for them
@@ -250,20 +248,17 @@ func (self *UndoController) hardResetWithAutoStash(commitHash string, options ha
 			HandleConfirm: func() error {
 				return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
 					if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + commitHash); err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					if err := reset(); err != nil {
 						return err
 					}
 
 					err := self.c.Git().Stash.Pop(0)
-					if err := self.c.Refresh(types.RefreshOptions{}); err != nil {
-						return err
-					}
 					if err != nil {
-						return self.c.Error(err)
+						return err
 					}
-					return nil
+					return self.c.Refresh(types.RefreshOptions{})
 				})
 			},
 		})
diff --git a/pkg/gui/controllers/workspace_reset_controller.go b/pkg/gui/controllers/workspace_reset_controller.go
index 5f3a9c6b9a3..3115d6f6890 100644
--- a/pkg/gui/controllers/workspace_reset_controller.go
+++ b/pkg/gui/controllers/workspace_reset_controller.go
@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"math"
 	"math/rand"
@@ -31,7 +32,7 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.NukeWorkingTree)
 				if err := self.c.Git().WorkingTree.ResetAndClean(); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				if self.c.UserConfig.Gui.AnimateExplosion {
@@ -53,7 +54,7 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedFileChanges)
 				if err := self.c.Git().WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(
@@ -70,7 +71,7 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.RemoveUntrackedFiles)
 				if err := self.c.Git().WorkingTree.RemoveUntrackedFiles(); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(
@@ -88,13 +89,13 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.RemoveStagedFiles)
 				if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
-					return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
+					return errors.New(self.c.Tr.NoTrackedStagedFilesStash)
 				}
 				if err := self.c.Git().Stash.SaveStagedChanges("[lazygit] tmp stash"); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 				if err := self.c.Git().Stash.DropNewest(); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(
@@ -111,7 +112,7 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.SoftReset)
 				if err := self.c.Git().WorkingTree.ResetSoft("HEAD"); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(
@@ -128,7 +129,7 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.MixedReset)
 				if err := self.c.Git().WorkingTree.ResetMixed("HEAD"); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(
@@ -145,7 +146,7 @@ func (self *FilesController) createResetMenu() error {
 			OnPress: func() error {
 				self.c.LogAction(self.c.Tr.Actions.HardReset)
 				if err := self.c.Git().WorkingTree.ResetHard("HEAD"); err != nil {
-					return self.c.Error(err)
+					return err
 				}
 
 				return self.c.Refresh(
@@ -180,7 +181,7 @@ func (self *FilesController) Explode(v *gocui.View, onDone func()) {
 		style.FgBlack.SetBold(),
 	}
 
-	self.c.OnWorker(func(_ gocui.Task) {
+	self.c.OnWorker(func(_ gocui.Task) error {
 		max := 25
 		for i := 0; i < max; i++ {
 			image := getExplodeImage(width, height, i, max)
@@ -198,6 +199,7 @@ func (self *FilesController) Explode(v *gocui.View, onDone func()) {
 			onDone()
 			return nil
 		})
+		return nil
 	})
 }
 
diff --git a/pkg/gui/controllers/worktrees_controller.go b/pkg/gui/controllers/worktrees_controller.go
index e9d15c02aa1..b9982c8c8d1 100644
--- a/pkg/gui/controllers/worktrees_controller.go
+++ b/pkg/gui/controllers/worktrees_controller.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"errors"
 	"fmt"
 	"strings"
 	"text/tabwriter"
@@ -117,11 +118,11 @@ func (self *WorktreesController) add() error {
 
 func (self *WorktreesController) remove(worktree *models.Worktree) error {
 	if worktree.IsMain {
-		return self.c.ErrorMsg(self.c.Tr.CantDeleteMainWorktree)
+		return errors.New(self.c.Tr.CantDeleteMainWorktree)
 	}
 
 	if worktree.IsCurrent {
-		return self.c.ErrorMsg(self.c.Tr.CantDeleteCurrentWorktree)
+		return errors.New(self.c.Tr.CantDeleteCurrentWorktree)
 	}
 
 	return self.c.Helpers().Worktree.Remove(worktree, false)
diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go
index 96132fea7c7..fd9540ea8aa 100644
--- a/pkg/gui/global_handlers.go
+++ b/pkg/gui/global_handlers.go
@@ -150,7 +150,7 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboardWithTruncation(maxWi
 
 	gui.c.LogAction(gui.c.Tr.Actions.CopyToClipboard)
 	if err := gui.os.CopyToClipboard(itemId); err != nil {
-		return gui.c.Error(err)
+		return err
 	}
 
 	truncatedItemId := utils.TruncateWithEllipsis(strings.Replace(itemId, "\n", " ", -1), 50)
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index 8a17bb2c278..4bb910bf292 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -516,8 +516,8 @@ func NewGui(
 		func() types.Context { return gui.State.ContextMgr.Current() },
 		gui.createMenu,
 		func(message string, f func(gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
-		func(message string, f func() error) {
-			gui.helpers.AppStatus.WithWaitingStatusSync(message, f)
+		func(message string, f func() error) error {
+			return gui.helpers.AppStatus.WithWaitingStatusSync(message, f)
 		},
 		func(message string, kind types.ToastKind) { gui.helpers.AppStatus.Toast(message, kind) },
 		func() string { return gui.Views.Confirmation.TextArea.GetContent() },
@@ -652,6 +652,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
 	gui.g = g
 	defer gui.g.Close()
 
+	g.ErrorHandler = gui.PopupHandler.ErrorHandler
+
 	// if the deadlock package wants to report a deadlock, we first need to
 	// close the gui so that we can actually read what it prints.
 	deadlock.Opts.LogBuf = utils.NewOnceWriter(os.Stderr, func() {
@@ -789,7 +791,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
 	defer gui.Mutexes.SubprocessMutex.Unlock()
 
 	if err := gui.g.Suspend(); err != nil {
-		return false, gui.c.Error(err)
+		return false, err
 	}
 
 	gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(true)
@@ -802,7 +804,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
 	}
 
 	if cmdErr != nil {
-		return false, gui.c.Error(cmdErr)
+		return false, cmdErr
 	}
 
 	return true, nil
@@ -956,7 +958,7 @@ func (gui *Gui) onUIThread(f func() error) {
 	})
 }
 
-func (gui *Gui) onWorker(f func(gocui.Task)) {
+func (gui *Gui) onWorker(f func(gocui.Task) error) {
 	gui.g.OnWorker(f)
 }
 
diff --git a/pkg/gui/gui_common.go b/pkg/gui/gui_common.go
index f4544031247..a75aa3658fc 100644
--- a/pkg/gui/gui_common.go
+++ b/pkg/gui/gui_common.go
@@ -151,7 +151,7 @@ func (self *guiCommon) OnUIThread(f func() error) {
 	self.gui.onUIThread(f)
 }
 
-func (self *guiCommon) OnWorker(f func(gocui.Task)) {
+func (self *guiCommon) OnWorker(f func(gocui.Task) error) {
 	self.gui.onWorker(f)
 }
 
diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go
index c6bb8ada5fd..83f72c07467 100644
--- a/pkg/gui/keybindings.go
+++ b/pkg/gui/keybindings.go
@@ -1,6 +1,7 @@
 package gui
 
 import (
+	"errors"
 	"log"
 
 	"github.com/jesseduffield/gocui"
@@ -442,7 +443,7 @@ func (gui *Gui) callKeybindingHandler(binding *types.Binding) error {
 	}
 	if disabledReason != nil {
 		if disabledReason.ShowErrorInPanel {
-			return gui.c.ErrorMsg(disabledReason.Text)
+			return errors.New(disabledReason.Text)
 		}
 
 		gui.c.ErrorToast(gui.Tr.DisabledMenuItemPrefix + disabledReason.Text)
diff --git a/pkg/gui/popup/popup_handler.go b/pkg/gui/popup/popup_handler.go
index 1eb81e80066..1ca9cc3eefb 100644
--- a/pkg/gui/popup/popup_handler.go
+++ b/pkg/gui/popup/popup_handler.go
@@ -8,20 +8,17 @@ import (
 	"github.com/jesseduffield/lazygit/pkg/common"
 	"github.com/jesseduffield/lazygit/pkg/gui/style"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
-	"github.com/sasha-s/go-deadlock"
 )
 
 type PopupHandler struct {
 	*common.Common
-	index int
-	deadlock.Mutex
 	createPopupPanelFn      func(context.Context, types.CreatePopupPanelOpts) error
 	onErrorFn               func() error
 	popContextFn            func() error
 	currentContextFn        func() types.Context
 	createMenuFn            func(types.CreateMenuOptions) error
 	withWaitingStatusFn     func(message string, f func(gocui.Task) error)
-	withWaitingStatusSyncFn func(message string, f func() error)
+	withWaitingStatusSyncFn func(message string, f func() error) error
 	toastFn                 func(message string, kind types.ToastKind)
 	getPromptInputFn        func() string
 	inDemo                  func() bool
@@ -37,14 +34,13 @@ func NewPopupHandler(
 	currentContextFn func() types.Context,
 	createMenuFn func(types.CreateMenuOptions) error,
 	withWaitingStatusFn func(message string, f func(gocui.Task) error),
-	withWaitingStatusSyncFn func(message string, f func() error),
+	withWaitingStatusSyncFn func(message string, f func() error) error,
 	toastFn func(message string, kind types.ToastKind),
 	getPromptInputFn func() string,
 	inDemo func() bool,
 ) *PopupHandler {
 	return &PopupHandler{
 		Common:                  common,
-		index:                   0,
 		createPopupPanelFn:      createPopupPanelFn,
 		onErrorFn:               onErrorFn,
 		popContextFn:            popContextFn,
@@ -80,25 +76,12 @@ func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) e
 }
 
 func (self *PopupHandler) WithWaitingStatusSync(message string, f func() error) error {
-	self.withWaitingStatusSyncFn(message, f)
-	return nil
+	return self.withWaitingStatusSyncFn(message, f)
 }
 
-func (self *PopupHandler) Error(err error) error {
-	if err == gocui.ErrQuit {
-		return err
-	}
-
-	return self.ErrorMsg(err.Error())
-}
-
-func (self *PopupHandler) ErrorMsg(message string) error {
-	self.Lock()
-	self.index++
-	self.Unlock()
-
+func (self *PopupHandler) ErrorHandler(err error) error {
 	// Need to set bold here explicitly; otherwise it gets cancelled by the red colouring.
-	coloredMessage := style.FgRed.SetBold().Sprint(strings.TrimSpace(message))
+	coloredMessage := style.FgRed.SetBold().Sprint(strings.TrimSpace(err.Error()))
 	if err := self.onErrorFn(); err != nil {
 		return err
 	}
@@ -111,10 +94,6 @@ func (self *PopupHandler) Alert(title string, message string) error {
 }
 
 func (self *PopupHandler) Confirm(opts types.ConfirmOpts) error {
-	self.Lock()
-	self.index++
-	self.Unlock()
-
 	return self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{
 		Title:         opts.Title,
 		Prompt:        opts.Prompt,
@@ -124,10 +103,6 @@ func (self *PopupHandler) Confirm(opts types.ConfirmOpts) error {
 }
 
 func (self *PopupHandler) Prompt(opts types.PromptOpts) error {
-	self.Lock()
-	self.index++
-	self.Unlock()
-
 	return self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{
 		Title:               opts.Title,
 		Prompt:              opts.InitialContent,
diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go
index d5dec287f80..9a127f3623f 100644
--- a/pkg/gui/services/custom_commands/handler_creator.go
+++ b/pkg/gui/services/custom_commands/handler_creator.go
@@ -1,6 +1,7 @@
 package custom_commands
 
 import (
+	"errors"
 	"fmt"
 	"strings"
 	"text/template"
@@ -74,7 +75,7 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
 				f = func() error {
 					resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
 					if err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return self.inputPrompt(resolvedPrompt, wrappedF)
 				}
@@ -82,7 +83,7 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
 				f = func() error {
 					resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
 					if err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return self.menuPrompt(resolvedPrompt, wrappedF)
 				}
@@ -90,7 +91,7 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
 				f = func() error {
 					resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
 					if err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return self.menuPromptFromCommand(resolvedPrompt, wrappedF)
 				}
@@ -98,12 +99,12 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
 				f = func() error {
 					resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
 					if err != nil {
-						return self.c.Error(err)
+						return err
 					}
 					return self.confirmPrompt(resolvedPrompt, g)
 				}
 			default:
-				return self.c.ErrorMsg("custom command prompt must have a type of 'input', 'menu', 'menuFromCommand', or 'confirm'")
+				return errors.New("custom command prompt must have a type of 'input', 'menu', 'menuFromCommand', or 'confirm'")
 			}
 		}
 
@@ -114,7 +115,7 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
 func (self *HandlerCreator) inputPrompt(prompt *config.CustomCommandPrompt, wrappedF func(string) error) error {
 	findSuggestionsFn, err := self.generateFindSuggestionsFunc(prompt)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	return self.c.Prompt(types.PromptOpts{
@@ -208,13 +209,13 @@ func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPr
 	// Run and save output
 	message, err := self.c.Git().Custom.RunWithOutput(prompt.Command)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	// Need to make a menu out of what the cmd has displayed
 	candidates, err := self.menuGenerator.call(message, prompt.Filter, prompt.ValueFormat, prompt.LabelFormat)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	menuItems := lo.Map(candidates, func(candidate *commandMenuItem, _ int) *types.MenuItem {
@@ -253,7 +254,7 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
 	resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
 	cmdStr, err := resolveTemplate(customCommand.Command)
 	if err != nil {
-		return self.c.Error(err)
+		return err
 	}
 
 	cmdObj := self.c.OS().Cmd.NewShell(cmdStr)
@@ -284,7 +285,7 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
 				return self.mergeAndRebaseHelper.CheckForConflicts(err)
 			}
 
-			return self.c.Error(err)
+			return err
 		}
 
 		if customCommand.ShowOutput {
diff --git a/pkg/gui/status/status_manager.go b/pkg/gui/status/status_manager.go
index b1433a6f98c..5cee4edc284 100644
--- a/pkg/gui/status/status_manager.go
+++ b/pkg/gui/status/status_manager.go
@@ -48,13 +48,12 @@ func NewStatusManager() *StatusManager {
 	return &StatusManager{}
 }
 
-func (self *StatusManager) WithWaitingStatus(message string, renderFunc func(), f func(*WaitingStatusHandle)) {
+func (self *StatusManager) WithWaitingStatus(message string, renderFunc func(), f func(*WaitingStatusHandle) error) error {
 	handle := &WaitingStatusHandle{statusManager: self, message: message, renderFunc: renderFunc, id: -1}
 	handle.Show()
+	defer handle.Hide()
 
-	f(handle)
-
-	handle.Hide()
+	return f(handle)
 }
 
 func (self *StatusManager) AddToastStatus(message string, kind types.ToastKind) int {
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index 694cdcc56a5..44f07c1e305 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -85,7 +85,7 @@ type IGuiCommon interface {
 	OnUIThread(f func() error)
 	// Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact
 	// that lazygit is still busy. See docs/dev/Busy.md
-	OnWorker(f func(gocui.Task))
+	OnWorker(f func(gocui.Task) error)
 	// Function to call at the end of our 'layout' function which renders views
 	// For example, you may want a view's line to be focused only after that view is
 	// resized, if in accordion mode.
@@ -131,11 +131,8 @@ type IModeMgr interface {
 }
 
 type IPopupHandler interface {
-	// Shows a popup with a (localized) "Error" caption and the given error message (in red).
-	//
-	// This is a convenience wrapper around Alert().
-	ErrorMsg(message string) error
-	Error(err error) error
+	// The global error handler for gocui. Not to be used by application code.
+	ErrorHandler(err error) error
 	// Shows a notification popup with the given title and message to the user.
 	//
 	// This is a convenience wrapper around Confirm(), thus the popup can be closed using both 'Enter' and 'ESC'.
diff --git a/pkg/gui/types/refresh.go b/pkg/gui/types/refresh.go
index c20a5f54ae5..2d6a383c3e6 100644
--- a/pkg/gui/types/refresh.go
+++ b/pkg/gui/types/refresh.go
@@ -33,7 +33,7 @@ const (
 )
 
 type RefreshOptions struct {
-	Then  func()
+	Then  func() error
 	Scope []RefreshableView // e.g. []RefreshableView{COMMITS, BRANCHES}. Leave empty to refresh everything
 	Mode  RefreshMode       // one of SYNC (default), ASYNC, and BLOCK_UI
 
diff --git a/pkg/tasks/async_handler.go b/pkg/tasks/async_handler.go
index 6f3f41b29fd..658687af9c3 100644
--- a/pkg/tasks/async_handler.go
+++ b/pkg/tasks/async_handler.go
@@ -18,10 +18,10 @@ type AsyncHandler struct {
 	lastId    int
 	mutex     deadlock.Mutex
 	onReject  func()
-	onWorker  func(func(gocui.Task))
+	onWorker  func(func(gocui.Task) error)
 }
 
-func NewAsyncHandler(onWorker func(func(gocui.Task))) *AsyncHandler {
+func NewAsyncHandler(onWorker func(func(gocui.Task) error)) *AsyncHandler {
 	return &AsyncHandler{
 		mutex:    deadlock.Mutex{},
 		onWorker: onWorker,
@@ -34,9 +34,10 @@ func (self *AsyncHandler) Do(f func() func()) {
 	id := self.currentId
 	self.mutex.Unlock()
 
-	self.onWorker(func(gocui.Task) {
+	self.onWorker(func(gocui.Task) error {
 		after := f()
 		self.handle(after, id)
+		return nil
 	})
 }
 
diff --git a/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md b/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000000..65527da0853
--- /dev/null
+++ b/vendor/github.com/gdamore/encoding/CODE_OF_CONDUCT.md
@@ -0,0 +1,73 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+  advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at garrett@damore.org. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
diff --git a/vendor/github.com/gdamore/encoding/README.md b/vendor/github.com/gdamore/encoding/README.md
index 3db2b4c58a5..2ce29a9f374 100644
--- a/vendor/github.com/gdamore/encoding/README.md
+++ b/vendor/github.com/gdamore/encoding/README.md
@@ -1,10 +1,11 @@
 ## encoding
 
-[![Linux Status](https://img.shields.io/travis/gdamore/encoding.svg?label=linux)](https://travis-ci.org/gdamore/encoding)
-[![Windows Status](https://img.shields.io/appveyor/ci/gdamore/encoding.svg?label=windows)](https://ci.appveyor.com/project/gdamore/encoding)
-[![Apache License](https://img.shields.io/badge/license-APACHE2-blue.svg)](https://github.com/gdamore/encoding/blob/master/LICENSE)
+
+[![Linux](https://img.shields.io/github/actions/workflow/status/gdamore/encoding/linux.yml?branch=main&logoColor=grey&logo=linux&label=)](https://github.com/gdamore/encoding/actions/workflows/linux.yml)
+[![Windows](https://img.shields.io/github/actions/workflow/status/gdamore/encoding/windows.yml?branch=main&logoColor=grey&logo=windows&label=)](https://github.com/gdamore/encoding/actions/workflows/windows.yml)
+[![Apache License](https://img.shields.io/github/license/gdamore/encoding.svg?logoColor=silver&logo=opensourceinitiative&color=blue&label=)](https://github.com/gdamore/encoding/blob/master/LICENSE)
+[![Coverage](https://img.shields.io/codecov/c/github/gdamore/encoding?logoColor=grey&logo=codecov&label=)](https://codecov.io/gh/gdamore/encoding)
 [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/gdamore/encoding)
-[![Go Report Card](http://goreportcard.com/badge/gdamore/encoding)](http://goreportcard.com/report/gdamore/encoding)
 
 Package encoding provides a number of encodings that are missing from the
 standard Go [encoding]("https://godoc.org/golang.org/x/text/encoding") package.
diff --git a/vendor/github.com/gdamore/encoding/SECURITY.md b/vendor/github.com/gdamore/encoding/SECURITY.md
new file mode 100644
index 00000000000..b9f64966f51
--- /dev/null
+++ b/vendor/github.com/gdamore/encoding/SECURITY.md
@@ -0,0 +1,12 @@
+# Security Policy
+
+We take security very seriously in mangos, since you may be using it in
+Internet-facing applications.
+
+## Reporting a Vulnerability
+
+To report a vulnerability, please contact us on our discord.
+You may also send an email to garrett@damore.org, or info@staysail.tech.
+
+We will keep the reporter updated on any status updates on a regular basis,
+and will respond within two business days for any reported security issue.
diff --git a/vendor/github.com/gdamore/encoding/charmap.go b/vendor/github.com/gdamore/encoding/charmap.go
index db1c33ef7ff..e8089c453a4 100644
--- a/vendor/github.com/gdamore/encoding/charmap.go
+++ b/vendor/github.com/gdamore/encoding/charmap.go
@@ -1,4 +1,4 @@
-// Copyright 2015 Garrett D'Amore
+// Copyright 2024 Garrett D'Amore
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use file except in compliance with the License.
@@ -52,7 +52,6 @@ const (
 // direction takes about 100 nsec/op.  (The larger cost for conversion
 // from UTF-8 is most likely due to the need to convert the UTF-8 byte stream
 // to a rune before conversion.
-//
 type Charmap struct {
 	transform.NopResetter
 	bytes map[rune]byte
@@ -177,7 +176,7 @@ func (d *cmapEncoder) Transform(dst, src []byte, atEOF bool) (int, int, error) {
 		if r == utf8.RuneError && sz == 1 {
 			// If its inconclusive due to insufficient data in
 			// in the source, report it
-			if !atEOF && !utf8.FullRune(src[nsrc:]) {
+			if atEOF && !utf8.FullRune(src[nsrc:]) {
 				e = transform.ErrShortSrc
 				break
 			}
diff --git a/vendor/github.com/jesseduffield/gocui/escape.go b/vendor/github.com/jesseduffield/gocui/escape.go
index 64c6b444824..b52c2149525 100644
--- a/vendor/github.com/jesseduffield/gocui/escape.go
+++ b/vendor/github.com/jesseduffield/gocui/escape.go
@@ -322,27 +322,6 @@ func (ei *escapeInterpreter) csiColor(param []string) (color Attribute, skip int
 	}
 }
 
-// splitFgBg splits foreground and background color according to ANSI sequence.
-//
-// num (number of segments in ansi) is used to determine if it's 256 mode or rgb mode (3 - 256-color, 5 - rgb-color)
-func splitFgBg(params []string, num int) [][]string {
-	var out [][]string
-	var current []string
-	for _, p := range params {
-		if len(current) == num && (p == "48" || p == "38") {
-			out = append(out, current)
-			current = []string{}
-		}
-		current = append(current, p)
-	}
-
-	if len(current) > 0 {
-		out = append(out, current)
-	}
-
-	return out
-}
-
 func getFontEffect(f int) Attribute {
 	switch fontEffect(f) {
 	case bold:
diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go
index 889839c45b0..e2593b98589 100644
--- a/vendor/github.com/jesseduffield/gocui/gui.go
+++ b/vendor/github.com/jesseduffield/gocui/gui.go
@@ -172,6 +172,8 @@ type Gui struct {
 	NextSearchMatchKey interface{}
 	PrevSearchMatchKey interface{}
 
+	ErrorHandler func(error) error
+
 	screen         tcell.Screen
 	suspendedMutex sync.Mutex
 	suspended      bool
@@ -661,7 +663,7 @@ func (g *Gui) updateAsyncAux(f func(*Gui) error, task Task) {
 // consider itself 'busy` as it runs the code. Don't use for long-running
 // background goroutines where you wouldn't want lazygit to be considered busy
 // (i.e. when you wouldn't want a loader to be shown to the user)
-func (g *Gui) OnWorker(f func(Task)) {
+func (g *Gui) OnWorker(f func(Task) error) {
 	task := g.NewTask()
 	go func() {
 		g.onWorkerAux(f, task)
@@ -669,7 +671,7 @@ func (g *Gui) OnWorker(f func(Task)) {
 	}()
 }
 
-func (g *Gui) onWorkerAux(f func(Task), task Task) {
+func (g *Gui) onWorkerAux(f func(Task) error, task Task) {
 	panicking := true
 	defer func() {
 		if panicking && Screen != nil {
@@ -677,9 +679,15 @@ func (g *Gui) onWorkerAux(f func(Task), task Task) {
 		}
 	}()
 
-	f(task)
+	err := f(task)
 
 	panicking = false
+
+	if err != nil {
+		g.Update(func(g *Gui) error {
+			return err
+		})
+	}
 }
 
 // A Manager is in charge of GUI's layout and can be used to build widgets.
@@ -745,19 +753,27 @@ func (g *Gui) MainLoop() error {
 	}
 }
 
+func (g *Gui) handleError(err error) error {
+	if err != nil && !IsQuit(err) && g.ErrorHandler != nil {
+		return g.ErrorHandler(err)
+	}
+
+	return err
+}
+
 func (g *Gui) processEvent() error {
 	select {
 	case ev := <-g.gEvents:
 		task := g.NewTask()
 		defer func() { task.Done() }()
 
-		if err := g.handleEvent(&ev); err != nil {
+		if err := g.handleError(g.handleEvent(&ev)); err != nil {
 			return err
 		}
 	case ev := <-g.userEvents:
 		defer func() { ev.task.Done() }()
 
-		if err := ev.f(g); err != nil {
+		if err := g.handleError(ev.f(g)); err != nil {
 			return err
 		}
 	}
@@ -777,11 +793,11 @@ func (g *Gui) processRemainingEvents() error {
 	for {
 		select {
 		case ev := <-g.gEvents:
-			if err := g.handleEvent(&ev); err != nil {
+			if err := g.handleError(g.handleEvent(&ev)); err != nil {
 				return err
 			}
 		case ev := <-g.userEvents:
-			err := ev.f(g)
+			err := g.handleError(ev.f(g))
 			ev.task.Done()
 			if err != nil {
 				return err
@@ -815,17 +831,6 @@ func (g *Gui) onResize() {
 	// g.screen.Sync()
 }
 
-func (g *Gui) clear(fg, bg Attribute) (int, int) {
-	st := getTcellStyle(oldStyle{fg: fg, bg: bg, outputMode: g.outputMode})
-	w, h := Screen.Size()
-	for row := 0; row < h; row++ {
-		for col := 0; col < w; col++ {
-			Screen.SetContent(col, row, ' ', nil, st)
-		}
-	}
-	return w, h
-}
-
 // drawFrameEdges draws the horizontal and vertical edges of a view.
 func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
 	runeH, runeV := '─', '│'
@@ -1397,7 +1402,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error)
 	var matchingParentViewKb *keybinding
 
 	// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
-	if v != nil && v.IsSearching() && Modifier(ev.Mod) == ModNone {
+	if v != nil && v.IsSearching() && ev.Mod == ModNone {
 		if eventMatchesKey(ev, g.NextSearchMatchKey) {
 			return true, v.gotoNextMatch()
 		} else if eventMatchesKey(ev, g.PrevSearchMatchKey) {
@@ -1417,7 +1422,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error)
 		if kb.handler == nil {
 			continue
 		}
-		if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) {
+		if !kb.matchKeypress(ev.Key, ev.Ch, ev.Mod) {
 			continue
 		}
 		if g.matchView(v, kb) {
@@ -1435,7 +1440,7 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) (matched bool, err error)
 	}
 
 	if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
-		matched := g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
+		matched := g.currentView.Editor.Edit(g.currentView, ev.Key, ev.Ch, ev.Mod)
 		if matched {
 			return true, nil
 		}
@@ -1550,7 +1555,7 @@ func (g *Gui) matchView(v *View, kb *keybinding) bool {
 	if v == nil {
 		return false
 	}
-	if v.Editable == true && kb.ch != 0 {
+	if v.Editable && kb.ch != 0 {
 		return false
 	}
 	if kb.viewName != v.name {
diff --git a/vendor/github.com/jesseduffield/gocui/gui_others.go b/vendor/github.com/jesseduffield/gocui/gui_others.go
index 578686fd69c..f0de7822a57 100644
--- a/vendor/github.com/jesseduffield/gocui/gui_others.go
+++ b/vendor/github.com/jesseduffield/gocui/gui_others.go
@@ -46,16 +46,14 @@ func (g *Gui) getTermWindowSize() (int, int, error) {
 			return termw, termh, nil
 		}
 
-		select {
-		case signal := <-signalCh:
-			switch signal {
-			// when the terminal window size is changed
-			case syscall.SIGWINCH:
-				continue
-			// ctrl + c to cancel
-			case syscall.SIGINT:
-				return 0, 0, errors.New("stop to get term window size")
-			}
+		signal := <-signalCh
+		switch signal {
+		// when the terminal window size is changed
+		case syscall.SIGWINCH:
+			continue
+		// ctrl + c to cancel
+		case syscall.SIGINT:
+			return 0, 0, errors.New("stop to get term window size")
 		}
 	}
 }
diff --git a/vendor/github.com/jesseduffield/gocui/gui_windows.go b/vendor/github.com/jesseduffield/gocui/gui_windows.go
index f643fdb331d..56c54570caf 100644
--- a/vendor/github.com/jesseduffield/gocui/gui_windows.go
+++ b/vendor/github.com/jesseduffield/gocui/gui_windows.go
@@ -13,10 +13,12 @@ import (
 	"unsafe"
 )
 
-type wchar uint16
-type short int16
-type dword uint32
-type word uint16
+type (
+	wchar uint16
+	short int16
+	dword uint32
+	word  uint16
+)
 
 type coord struct {
 	x short
diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go
index e2b931d7e23..0d2cecc68c8 100644
--- a/vendor/github.com/jesseduffield/gocui/keybinding.go
+++ b/vendor/github.com/jesseduffield/gocui/keybinding.go
@@ -105,7 +105,7 @@ func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func
 
 func eventMatchesKey(ev *GocuiEvent, key interface{}) bool {
 	// assuming ModNone for now
-	if Modifier(ev.Mod) != ModNone {
+	if ev.Mod != ModNone {
 		return false
 	}
 
@@ -114,7 +114,7 @@ func eventMatchesKey(ev *GocuiEvent, key interface{}) bool {
 		return false
 	}
 
-	return k == Key(ev.Key) && ch == ev.Ch
+	return k == ev.Key && ch == ev.Ch
 }
 
 // matchKeypress returns if the keybinding matches the keypress.
diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go
index 6bfc4c48712..a84e1b0ce8a 100644
--- a/vendor/github.com/jesseduffield/gocui/view.go
+++ b/vendor/github.com/jesseduffield/gocui/view.go
@@ -767,7 +767,7 @@ func (v *View) writeString(s string) {
 // parseInput parses char by char the input written to the View. It returns nil
 // while processing ESC sequences. Otherwise, it returns a cell slice that
 // contains the processed data.
-func (v *View) parseInput(ch rune, x int, y int) (bool, []cell) {
+func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) {
 	cells := []cell{}
 	moveCursor := true
 
@@ -1283,7 +1283,7 @@ func (v *View) Word(x, y int) (string, error) {
 	} else {
 		nr = nr + x
 	}
-	return string(str[nl:nr]), nil
+	return str[nl:nr], nil
 }
 
 // indexFunc allows to split lines by words taking into account spaces
diff --git a/vendor/golang.org/x/sys/unix/mmap_nomremap.go b/vendor/golang.org/x/sys/unix/mmap_nomremap.go
index 4b68e59780a..7f602ffd26d 100644
--- a/vendor/golang.org/x/sys/unix/mmap_nomremap.go
+++ b/vendor/golang.org/x/sys/unix/mmap_nomremap.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris
+//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris || zos
 
 package unix
 
diff --git a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
index b473038c615..27c41b6f0a1 100644
--- a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
+++ b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go
@@ -1520,6 +1520,14 @@ func (m *mmapper) Munmap(data []byte) (err error) {
 	return nil
 }
 
+func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
+        return mapper.Mmap(fd, offset, length, prot, flags)
+}
+
+func Munmap(b []byte) (err error) {
+        return mapper.Munmap(b)
+}
+
 func Read(fd int, p []byte) (n int, err error) {
 	n, err = read(fd, p)
 	if raceenabled {
diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go
index 6395a031d45..6525c62f3c2 100644
--- a/vendor/golang.org/x/sys/windows/syscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/syscall_windows.go
@@ -165,6 +165,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
 //sys	CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW
 //sys	CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error)  [failretval==InvalidHandle] = CreateNamedPipeW
 //sys	ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error)
+//sys	DisconnectNamedPipe(pipe Handle) (err error)
 //sys	GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error)
 //sys	GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
 //sys	SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState
@@ -348,8 +349,19 @@ func NewCallbackCDecl(fn interface{}) uintptr {
 //sys	SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost
 //sys	GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32)
 //sys	SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error)
+//sys	ClearCommBreak(handle Handle) (err error)
+//sys	ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error)
+//sys	EscapeCommFunction(handle Handle, dwFunc uint32) (err error)
+//sys	GetCommState(handle Handle, lpDCB *DCB) (err error)
+//sys	GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error)
 //sys	GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
+//sys	PurgeComm(handle Handle, dwFlags uint32) (err error)
+//sys	SetCommBreak(handle Handle) (err error)
+//sys	SetCommMask(handle Handle, dwEvtMask uint32) (err error)
+//sys	SetCommState(handle Handle, lpDCB *DCB) (err error)
 //sys	SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
+//sys	SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error)
+//sys	WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error)
 //sys	GetActiveProcessorCount(groupNumber uint16) (ret uint32)
 //sys	GetMaximumProcessorCount(groupNumber uint16) (ret uint32)
 //sys	EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows
@@ -1834,3 +1846,73 @@ func ResizePseudoConsole(pconsole Handle, size Coord) error {
 	// accept arguments that can be casted to uintptr, and Coord can't.
 	return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size))))
 }
+
+// DCB constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.
+const (
+	CBR_110    = 110
+	CBR_300    = 300
+	CBR_600    = 600
+	CBR_1200   = 1200
+	CBR_2400   = 2400
+	CBR_4800   = 4800
+	CBR_9600   = 9600
+	CBR_14400  = 14400
+	CBR_19200  = 19200
+	CBR_38400  = 38400
+	CBR_57600  = 57600
+	CBR_115200 = 115200
+	CBR_128000 = 128000
+	CBR_256000 = 256000
+
+	DTR_CONTROL_DISABLE   = 0x00000000
+	DTR_CONTROL_ENABLE    = 0x00000010
+	DTR_CONTROL_HANDSHAKE = 0x00000020
+
+	RTS_CONTROL_DISABLE   = 0x00000000
+	RTS_CONTROL_ENABLE    = 0x00001000
+	RTS_CONTROL_HANDSHAKE = 0x00002000
+	RTS_CONTROL_TOGGLE    = 0x00003000
+
+	NOPARITY    = 0
+	ODDPARITY   = 1
+	EVENPARITY  = 2
+	MARKPARITY  = 3
+	SPACEPARITY = 4
+
+	ONESTOPBIT   = 0
+	ONE5STOPBITS = 1
+	TWOSTOPBITS  = 2
+)
+
+// EscapeCommFunction constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-escapecommfunction.
+const (
+	SETXOFF  = 1
+	SETXON   = 2
+	SETRTS   = 3
+	CLRRTS   = 4
+	SETDTR   = 5
+	CLRDTR   = 6
+	SETBREAK = 8
+	CLRBREAK = 9
+)
+
+// PurgeComm constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-purgecomm.
+const (
+	PURGE_TXABORT = 0x0001
+	PURGE_RXABORT = 0x0002
+	PURGE_TXCLEAR = 0x0004
+	PURGE_RXCLEAR = 0x0008
+)
+
+// SetCommMask constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcommmask.
+const (
+	EV_RXCHAR  = 0x0001
+	EV_RXFLAG  = 0x0002
+	EV_TXEMPTY = 0x0004
+	EV_CTS     = 0x0008
+	EV_DSR     = 0x0010
+	EV_RLSD    = 0x0020
+	EV_BREAK   = 0x0040
+	EV_ERR     = 0x0080
+	EV_RING    = 0x0100
+)
diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go
index 359780f6ace..d8cb71db0a6 100644
--- a/vendor/golang.org/x/sys/windows/types_windows.go
+++ b/vendor/golang.org/x/sys/windows/types_windows.go
@@ -3380,3 +3380,27 @@ type BLOB struct {
 	Size     uint32
 	BlobData *byte
 }
+
+type ComStat struct {
+	Flags    uint32
+	CBInQue  uint32
+	CBOutQue uint32
+}
+
+type DCB struct {
+	DCBlength  uint32
+	BaudRate   uint32
+	Flags      uint32
+	wReserved  uint16
+	XonLim     uint16
+	XoffLim    uint16
+	ByteSize   uint8
+	Parity     uint8
+	StopBits   uint8
+	XonChar    byte
+	XoffChar   byte
+	ErrorChar  byte
+	EofChar    byte
+	EvtChar    byte
+	wReserved1 uint16
+}
diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
index e8791c82c30..5c6035ddfa9 100644
--- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
@@ -188,6 +188,8 @@ var (
 	procAssignProcessToJobObject                             = modkernel32.NewProc("AssignProcessToJobObject")
 	procCancelIo                                             = modkernel32.NewProc("CancelIo")
 	procCancelIoEx                                           = modkernel32.NewProc("CancelIoEx")
+	procClearCommBreak                                       = modkernel32.NewProc("ClearCommBreak")
+	procClearCommError                                       = modkernel32.NewProc("ClearCommError")
 	procCloseHandle                                          = modkernel32.NewProc("CloseHandle")
 	procClosePseudoConsole                                   = modkernel32.NewProc("ClosePseudoConsole")
 	procConnectNamedPipe                                     = modkernel32.NewProc("ConnectNamedPipe")
@@ -212,7 +214,9 @@ var (
 	procDeleteProcThreadAttributeList                        = modkernel32.NewProc("DeleteProcThreadAttributeList")
 	procDeleteVolumeMountPointW                              = modkernel32.NewProc("DeleteVolumeMountPointW")
 	procDeviceIoControl                                      = modkernel32.NewProc("DeviceIoControl")
+	procDisconnectNamedPipe                                  = modkernel32.NewProc("DisconnectNamedPipe")
 	procDuplicateHandle                                      = modkernel32.NewProc("DuplicateHandle")
+	procEscapeCommFunction                                   = modkernel32.NewProc("EscapeCommFunction")
 	procExitProcess                                          = modkernel32.NewProc("ExitProcess")
 	procExpandEnvironmentStringsW                            = modkernel32.NewProc("ExpandEnvironmentStringsW")
 	procFindClose                                            = modkernel32.NewProc("FindClose")
@@ -236,6 +240,8 @@ var (
 	procGenerateConsoleCtrlEvent                             = modkernel32.NewProc("GenerateConsoleCtrlEvent")
 	procGetACP                                               = modkernel32.NewProc("GetACP")
 	procGetActiveProcessorCount                              = modkernel32.NewProc("GetActiveProcessorCount")
+	procGetCommModemStatus                                   = modkernel32.NewProc("GetCommModemStatus")
+	procGetCommState                                         = modkernel32.NewProc("GetCommState")
 	procGetCommTimeouts                                      = modkernel32.NewProc("GetCommTimeouts")
 	procGetCommandLineW                                      = modkernel32.NewProc("GetCommandLineW")
 	procGetComputerNameExW                                   = modkernel32.NewProc("GetComputerNameExW")
@@ -322,6 +328,7 @@ var (
 	procProcess32NextW                                       = modkernel32.NewProc("Process32NextW")
 	procProcessIdToSessionId                                 = modkernel32.NewProc("ProcessIdToSessionId")
 	procPulseEvent                                           = modkernel32.NewProc("PulseEvent")
+	procPurgeComm                                            = modkernel32.NewProc("PurgeComm")
 	procQueryDosDeviceW                                      = modkernel32.NewProc("QueryDosDeviceW")
 	procQueryFullProcessImageNameW                           = modkernel32.NewProc("QueryFullProcessImageNameW")
 	procQueryInformationJobObject                            = modkernel32.NewProc("QueryInformationJobObject")
@@ -335,6 +342,9 @@ var (
 	procResetEvent                                           = modkernel32.NewProc("ResetEvent")
 	procResizePseudoConsole                                  = modkernel32.NewProc("ResizePseudoConsole")
 	procResumeThread                                         = modkernel32.NewProc("ResumeThread")
+	procSetCommBreak                                         = modkernel32.NewProc("SetCommBreak")
+	procSetCommMask                                          = modkernel32.NewProc("SetCommMask")
+	procSetCommState                                         = modkernel32.NewProc("SetCommState")
 	procSetCommTimeouts                                      = modkernel32.NewProc("SetCommTimeouts")
 	procSetConsoleCursorPosition                             = modkernel32.NewProc("SetConsoleCursorPosition")
 	procSetConsoleMode                                       = modkernel32.NewProc("SetConsoleMode")
@@ -342,7 +352,6 @@ var (
 	procSetDefaultDllDirectories                             = modkernel32.NewProc("SetDefaultDllDirectories")
 	procSetDllDirectoryW                                     = modkernel32.NewProc("SetDllDirectoryW")
 	procSetEndOfFile                                         = modkernel32.NewProc("SetEndOfFile")
-	procSetFileValidData                                     = modkernel32.NewProc("SetFileValidData")
 	procSetEnvironmentVariableW                              = modkernel32.NewProc("SetEnvironmentVariableW")
 	procSetErrorMode                                         = modkernel32.NewProc("SetErrorMode")
 	procSetEvent                                             = modkernel32.NewProc("SetEvent")
@@ -351,6 +360,7 @@ var (
 	procSetFileInformationByHandle                           = modkernel32.NewProc("SetFileInformationByHandle")
 	procSetFilePointer                                       = modkernel32.NewProc("SetFilePointer")
 	procSetFileTime                                          = modkernel32.NewProc("SetFileTime")
+	procSetFileValidData                                     = modkernel32.NewProc("SetFileValidData")
 	procSetHandleInformation                                 = modkernel32.NewProc("SetHandleInformation")
 	procSetInformationJobObject                              = modkernel32.NewProc("SetInformationJobObject")
 	procSetNamedPipeHandleState                              = modkernel32.NewProc("SetNamedPipeHandleState")
@@ -361,6 +371,7 @@ var (
 	procSetStdHandle                                         = modkernel32.NewProc("SetStdHandle")
 	procSetVolumeLabelW                                      = modkernel32.NewProc("SetVolumeLabelW")
 	procSetVolumeMountPointW                                 = modkernel32.NewProc("SetVolumeMountPointW")
+	procSetupComm                                            = modkernel32.NewProc("SetupComm")
 	procSizeofResource                                       = modkernel32.NewProc("SizeofResource")
 	procSleepEx                                              = modkernel32.NewProc("SleepEx")
 	procTerminateJobObject                                   = modkernel32.NewProc("TerminateJobObject")
@@ -379,6 +390,7 @@ var (
 	procVirtualQueryEx                                       = modkernel32.NewProc("VirtualQueryEx")
 	procVirtualUnlock                                        = modkernel32.NewProc("VirtualUnlock")
 	procWTSGetActiveConsoleSessionId                         = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
+	procWaitCommEvent                                        = modkernel32.NewProc("WaitCommEvent")
 	procWaitForMultipleObjects                               = modkernel32.NewProc("WaitForMultipleObjects")
 	procWaitForSingleObject                                  = modkernel32.NewProc("WaitForSingleObject")
 	procWriteConsoleW                                        = modkernel32.NewProc("WriteConsoleW")
@@ -1641,6 +1653,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) {
 	return
 }
 
+func ClearCommBreak(handle Handle) (err error) {
+	r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) {
+	r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat)))
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func CloseHandle(handle Handle) (err error) {
 	r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
 	if r1 == 0 {
@@ -1845,6 +1873,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff
 	return
 }
 
+func DisconnectNamedPipe(pipe Handle) (err error) {
+	r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(pipe), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) {
 	var _p0 uint32
 	if bInheritHandle {
@@ -1857,6 +1893,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP
 	return
 }
 
+func EscapeCommFunction(handle Handle, dwFunc uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(dwFunc), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func ExitProcess(exitcode uint32) {
 	syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0)
 	return
@@ -2058,6 +2102,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
 	return
 }
 
+func GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpModemStat)), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func GetCommState(handle Handle, lpDCB *DCB) (err error) {
+	r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
 	r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
 	if r1 == 0 {
@@ -2810,6 +2870,14 @@ func PulseEvent(event Handle) (err error) {
 	return
 }
 
+func PurgeComm(handle Handle, dwFlags uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(dwFlags), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) {
 	r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max))
 	n = uint32(r0)
@@ -2924,6 +2992,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) {
 	return
 }
 
+func SetCommBreak(handle Handle) (err error) {
+	r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func SetCommMask(handle Handle, dwEvtMask uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(dwEvtMask), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+func SetCommState(handle Handle, lpDCB *DCB) (err error) {
+	r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
 	r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
 	if r1 == 0 {
@@ -2989,14 +3081,6 @@ func SetEndOfFile(handle Handle) (err error) {
 	return
 }
 
-func SetFileValidData(handle Handle, validDataLength int64) (err error) {
-	r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
-	if r1 == 0 {
-		err = errnoErr(e1)
-	}
-	return
-}
-
 func SetEnvironmentVariable(name *uint16, value *uint16) (err error) {
 	r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0)
 	if r1 == 0 {
@@ -3060,6 +3144,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim
 	return
 }
 
+func SetFileValidData(handle Handle, validDataLength int64) (err error) {
+	r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) {
 	r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags))
 	if r1 == 0 {
@@ -3145,6 +3237,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro
 	return
 }
 
+func SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) {
+	r1, _, e1 := syscall.Syscall(procSetupComm.Addr(), 3, uintptr(handle), uintptr(dwInQueue), uintptr(dwOutQueue))
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) {
 	r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0)
 	size = uint32(r0)
@@ -3291,6 +3391,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) {
 	return
 }
 
+func WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) {
+	r1, _, e1 := syscall.Syscall(procWaitCommEvent.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpEvtMask)), uintptr(unsafe.Pointer(lpOverlapped)))
+	if r1 == 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
 func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) {
 	var _p0 uint32
 	if waitAll {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 667f3e97183..9bedc28a225 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -37,7 +37,7 @@ github.com/fatih/color
 # github.com/fsmiamoto/git-todo-parser v0.0.5
 ## explicit; go 1.13
 github.com/fsmiamoto/git-todo-parser/todo
-# github.com/gdamore/encoding v1.0.0
+# github.com/gdamore/encoding v1.0.1
 ## explicit; go 1.9
 github.com/gdamore/encoding
 # github.com/gdamore/tcell/v2 v2.7.4
@@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
 github.com/jesseduffield/go-git/v5/utils/merkletrie/index
 github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
 github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
-# github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de
+# github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513
 ## explicit; go 1.12
 github.com/jesseduffield/gocui
 # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
@@ -310,13 +310,13 @@ golang.org/x/exp/slices
 golang.org/x/net/context
 golang.org/x/net/internal/socks
 golang.org/x/net/proxy
-# golang.org/x/sys v0.18.0
+# golang.org/x/sys v0.19.0
 ## explicit; go 1.18
 golang.org/x/sys/cpu
 golang.org/x/sys/plan9
 golang.org/x/sys/unix
 golang.org/x/sys/windows
-# golang.org/x/term v0.18.0
+# golang.org/x/term v0.19.0
 ## explicit; go 1.18
 golang.org/x/term
 # golang.org/x/text v0.14.0


Test patch:
diff --git a/pkg/tasks/async_handler_test.go b/pkg/tasks/async_handler_test.go
index ebead7aa72e..6da363cd964 100644
--- a/pkg/tasks/async_handler_test.go
+++ b/pkg/tasks/async_handler_test.go
@@ -13,8 +13,8 @@ func TestAsyncHandler(t *testing.T) {
 	wg := sync.WaitGroup{}
 	wg.Add(2)
 
-	onWorker := func(f func(gocui.Task)) {
-		go f(gocui.NewFakeTask())
+	onWorker := func(f func(gocui.Task) error) {
+		go func() { _ = f(gocui.NewFakeTask()) }()
 	}
 	handler := NewAsyncHandler(onWorker)
 	handler.onReject = func() {