From baedc28232f80b434664c1a85f957c4848c1e84c Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Wed, 11 Feb 2026 17:19:51 -0300 Subject: [PATCH] fix: address potential panic on shell command execution (#2200) This panic happen once in a while on CI on Windows specifically. I personally never saw it happening myself, but I think it's possible to happen for the end user on Windows as well. Looks like a potential bug on the interpreter, but in the meantime let's at least recover from the panic and gracefully handle it. panic: ended up with a non-nil exitStatus.err but a zero exitStatus.code goroutine 61 [running]: mvdan.cc/sh/v3/interp.(*Runner).Run(0xc000220848, {0x1415220e0, 0xc00021a1e0}, {0x14151e088, 0xc00025a600}) C:/Users/runneradmin/go/pkg/mod/mvdan.cc/sh/v3@v3.12.1-0.20250902163504-3cf4fd5717a5/interp/api.go:929 +0x6b2 github.com/charmbracelet/crush/internal/shell.(*Shell).execCommon(0xc000256360, {0x1415220e0, 0xc00021a1e0}, {0x14135a250, 0x9}, {0x14151baa0, 0xc00025a540}, {0x14151baa0, 0xc00025a580}) D:/a/crush/crush/internal/shell/shell.go:273 +0x285 github.com/charmbracelet/crush/internal/shell.(*Shell).execStream(...) D:/a/crush/crush/internal/shell/shell.go:288 github.com/charmbracelet/crush/internal/shell.(*Shell).ExecStream(0xc000256360, {0x1415220e0, 0xc00021a1e0}, {0x14135a250, 0x9}, {0x14151baa0, 0xc00025a540}, {0x14151baa0, 0xc00025a580}) D:/a/crush/crush/internal/shell/shell.go:111 +0x139 github.com/charmbracelet/crush/internal/shell.(*BackgroundShellManager).Start.func1() D:/a/crush/crush/internal/shell/background.go:122 +0x15f created by github.com/charmbracelet/crush/internal/shell.(*BackgroundShellManager).Start in goroutine 28 D:/a/crush/crush/internal/shell/background.go:119 +0x72a --- internal/shell/shell.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/shell/shell.go b/internal/shell/shell.go index d8dde82a0077d3be5cd19c2714e5a1a5097d015c..580ab3c5d592f6e2f41bbf65dfea6990d3b35b2f 100644 --- a/internal/shell/shell.go +++ b/internal/shell/shell.go @@ -259,20 +259,29 @@ func (s *Shell) updateShellFromRunner(runner *interp.Runner) { } // execCommon is the shared implementation for executing commands -func (s *Shell) execCommon(ctx context.Context, command string, stdout, stderr io.Writer) error { +func (s *Shell) execCommon(ctx context.Context, command string, stdout, stderr io.Writer) (err error) { + var runner *interp.Runner + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("command execution panic: %v", r) + } + if runner != nil { + s.updateShellFromRunner(runner) + } + s.logger.InfoPersist("command finished", "command", command, "err", err) + }() + line, err := syntax.NewParser().Parse(strings.NewReader(command), "") if err != nil { return fmt.Errorf("could not parse command: %w", err) } - runner, err := s.newInterp(stdout, stderr) + runner, err = s.newInterp(stdout, stderr) if err != nil { return fmt.Errorf("could not run command: %w", err) } err = runner.Run(ctx, line) - s.updateShellFromRunner(runner) - s.logger.InfoPersist("command finished", "command", command, "err", err) return err }