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
- Read the recovered value in the message to identify the panicking component.
- Minimize the command to the smallest snippet that reproduces the panic and file/check an issue against mvdan/sh.
- Update the mvdan/sh dependency — interpreter panics are usually fixed upstream.
- 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
- Keep the mvdan/sh dependency updated to pick up interpreter fixes.
- Add recover() inside custom builtin handlers so builtins can't crash the runner.
- Sanitize untrusted shell input; fuzz-test the commands you pass through.
- Minimize exotic shell constructs (deep recursion, giant expansions) in generated commands.
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.