charmbracelet/crush · error

command execution panic: %v

Error message

command execution panic: %v

What it means

Shell.execCommon wraps command execution in a deferred recover(). If the mvdan/sh interpreter (interp.Runner) panics during execution — a library-level bug or pathological input that crashes the interpreter — the panic is converted into a regular error "command execution panic: %v" instead of crashing the process. The runner state is still synced from the interpreter afterwards.

Source

Thrown at internal/shell/shell.go:263

}

// updateShellFromRunner updates the shell from the interpreter after execution.
func (s *Shell) updateShellFromRunner(runner *interp.Runner) {
	s.cwd = runner.Dir
	s.env = s.env[:0]
	for name, vr := range runner.Vars {
		if vr.Exported {
			s.env = append(s.env, name+"="+vr.Str)
		}
	}
}

// execCommon is the shared implementation for executing commands
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(nil, stdout, stderr)
	if err != nil {
		return fmt.Errorf("could not run command: %w", err)
	}

	err = runner.Run(ctx, line)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the recovered value in the message to identify the panicking component.
  2. Minimize the command to the smallest snippet that reproduces the panic and file/check an issue against mvdan/sh.
  3. Update the mvdan/sh dependency — interpreter panics are usually fixed upstream.
  4. If a custom builtin panics, add its own recover() inside the builtin handler.

Example fix

// before
go get mvdan.cc/sh/v3@v3.7.0
// after
go get -u mvdan.cc/sh/v3
Defensive patterns

Strategy: try-catch

Try / catch

err := sh.Exec(ctx, cmd)
if err != nil && strings.HasPrefix(err.Error(), "command execution panic") {
	// report bug with cmd + panic value; do not retry blindly
	logger.Error("interpreter panic", "cmd", cmd, "err", err)
}

Prevention

When it happens

Trigger: A panic inside mvdan/sh's interp.Runner during runner.Run(ctx, line) — e.g. interpreter bugs on exotic shell constructs, nil map/function bugs in builtins, or stack exhaustion. Not triggered by normal shell errors (those return as ordinary errors).

Common situations: Running hostile or unusual shell input through the embedded interpreter; hitting upstream mvdan/sh bugs on a specific version; custom builtin handlers that panic.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/a43bee960811f923. Report an issue: GitHub.