charmbracelet/crush · error
could not build runner for %s: %w
Error message
could not build runner for %s: %w
What it means
runShellSource wraps a failure from interp.New when constructing the nested interpreter runner that executes a parsed shell file. Runner construction validates options (StdIO, Env, Dir, Params, exec handler); an invalid combination makes the runner unbuildable before any script line executes.
Source
Thrown at internal/shell/dispatch.go:408
opts := []interp.RunnerOption{
interp.StdIO(hc.Stdin, hc.Stdout, hc.Stderr),
interp.Interactive(false),
interp.Env(hc.Env),
interp.Dir(hc.Dir),
execHandlerOption(blockFuncs),
}
if len(args) > 1 {
// Params with a leading "--" avoids any of args[1:] being
// misinterpreted as set-options (e.g. a user passing "-e" as
// a positional arg to their script).
params := append([]string{"--"}, args[1:]...)
opts = append(opts, interp.Params(params...))
}
runner, err := interp.New(opts...)
if err != nil {
return fmt.Errorf("could not build runner for %s: %w", path, err)
}
return runner.Run(ctx, file)
}
// execEnvList converts an expand.Environ to the []string form that
// os/exec.Cmd.Env expects. Only exported string variables are included,
// matching what a real shell would pass to a child process.
func execEnvList(env expand.Environ) []string {
var out []string
env.Each(func(name string, vr expand.Variable) bool {
if vr.Exported && vr.Kind == expand.String {
out = append(out, name+"="+vr.Str)
}
return true
})
return out
}
View on GitHub (pinned to 7944b8e522)
Solutions
- Inspect the wrapped mvdan.cc/sh error to identify which runner option is invalid.
- Verify the parent interp.HandlerCtx Dir/Env values are sane (existing directory, well-formed env list).
- Check that args passed via interp.Params don't include malformed set-options; the code prefixes "--" to guard this.
- If it persists after a mvdan.cc/sh upgrade, pin the previous version and report the incompatibility.
Defensive patterns
Strategy: try-catch
Validate before calling
if st, err := os.Stat(dir); err != nil || !st.IsDir() {
// bad Dir option would break interp.New — fix before dispatch
} Try / catch
if err != nil {
if strings.HasPrefix(err.Error(), "could not build runner for ") {
// runner construction failed; inspect wrapped interp.New error
}
} Prevention
- Keep runner options (Dir, Env, Params) derived from validated values.
- Preserve the "--" prefix when forwarding positional params.
- Test the dispatch path after mvdan.cc/sh upgrades.
- Keep exec handler wiring in one reviewed place.
When it happens
Trigger: After a successful syntax parse, interp.New(opts...) returns an error — e.g. invalid interp.Params values, an unusable interp.Dir, or a misconfigured exec handler option — while dispatching a shell script in-process.
Common situations: Programmatic misconfiguration of runner options; a parent HandlerCtx carrying a bad working directory; regression in custom execHandlerOption wiring after refactors; rare mvdan.cc/sh version incompatibilities.
Related errors
- interpreter %q not found in PATH
- interpreter %q not found and %q not in PATH
- parse: %w
- requested channels differ from the existing workspace; chann
- empty shebang
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/ae27899fef74ca3a.
Report an issue: GitHub.