{"record":{"id":"a43bee960811f923","repo":"charmbracelet/crush","slug":"command-execution-panic-v-a43bee","errorCode":null,"errorMessage":"command execution panic: %v","messagePattern":"command execution panic: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/shell/shell.go","lineNumber":263,"sourceCode":"}\n\n// updateShellFromRunner updates the shell from the interpreter after execution.\nfunc (s *Shell) updateShellFromRunner(runner *interp.Runner) {\n\ts.cwd = runner.Dir\n\ts.env = s.env[:0]\n\tfor name, vr := range runner.Vars {\n\t\tif vr.Exported {\n\t\t\ts.env = append(s.env, name+\"=\"+vr.Str)\n\t\t}\n\t}\n}\n\n// execCommon is the shared implementation for executing commands\nfunc (s *Shell) execCommon(ctx context.Context, command string, stdout, stderr io.Writer) (err error) {\n\tvar runner *interp.Runner\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"command execution panic: %v\", r)\n\t\t}\n\t\tif runner != nil {\n\t\t\ts.updateShellFromRunner(runner)\n\t\t}\n\t\ts.logger.InfoPersist(\"command finished\", \"command\", command, \"err\", err)\n\t}()\n\n\tline, err := syntax.NewParser().Parse(strings.NewReader(command), \"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not parse command: %w\", err)\n\t}\n\n\trunner, err = s.newInterp(nil, stdout, stderr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not run command: %w\", err)\n\t}\n\n\terr = runner.Run(ctx, line)","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/shell/shell.go#L245-L281","documentation":"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.","triggerScenarios":"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).","commonSituations":"Running hostile or unusual shell input through the embedded interpreter; hitting upstream mvdan/sh bugs on a specific version; custom builtin handlers that panic.","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."],"exampleFix":"// before\ngo get mvdan.cc/sh/v3@v3.7.0\n// after\ngo get -u mvdan.cc/sh/v3","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := sh.Exec(ctx, cmd)\nif err != nil && strings.HasPrefix(err.Error(), \"command execution panic\") {\n\t// report bug with cmd + panic value; do not retry blindly\n\tlogger.Error(\"interpreter panic\", \"cmd\", cmd, \"err\", err)\n}","preventionTips":["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."],"tags":["panic","shell-interpreter","recovery"],"backgroundTag":"interpreter-panic","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}