charmbracelet/crush · error

could not parse %s: %w

Error message

could not parse %s: %w

What it means

runShellSource wraps a syntax parse failure when executing a file's contents as POSIX shell in-process via mvdan.cc/sh. The file could not be parsed by syntax.NewParser().Parse, so the wrapped interpreter never runs it. The underlying mvdan.cc/sh parse error is included via %w.

Source

Thrown at internal/shell/dispatch.go:386

// runShellSource parses path's contents as POSIX shell and runs it
// in-process via a nested interp.Runner. It reuses the parent runner's cwd,
// env, and stdio, and rebuilds the Crush handler stack so builtins and the
// dispatch handler itself remain available to anything the script invokes.
// Positional parameters ($1, $2, …) come from args[1:].
//
// This is the only branch that reads the full file; probeFile keeps its
// read to probeWindow bytes so the binary/shebang paths never touch more
// than 128 bytes of I/O.
func runShellSource(ctx context.Context, path string, args []string, blockFuncs []BlockFunc) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	file, err := syntax.NewParser().Parse(bytes.NewReader(data), path)
	if err != nil {
		return fmt.Errorf("could not parse %s: %w", path, err)
	}

	hc := interp.HandlerCtx(ctx)

	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...))
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the wrapped mvdan.cc/sh error (includes line/column) and fix the syntax at that location.
  2. Validate the script locally with `sh -n script.sh` (or bash -n if bashisms are intended) before shipping.
  3. If the script requires bash-only syntax, ensure the shebang targets bash and the dispatcher executes it as such.
  4. Regenerate or re-download the file if it appears truncated or templated incorrectly.

Example fix

// before (invalid: unmatched quote)
echo "hello world
// after
echo "hello world"
Defensive patterns

Strategy: try-catch

Validate before calling

if err := exec.Command("sh", "-n", scriptPath).Run(); err != nil {
    // script has syntax problems; fix before executing via dispatcher
}

Try / catch

if err != nil {
    if strings.HasPrefix(err.Error(), "could not parse ") {
        // parse failure: log path + wrapped mvdan.cc/sh position info
    }
}

Prevention

When it happens

Trigger: dispatchShebang or the binary/exec handler resolves a script to shell source and runShellSource reads it; the file contains syntax invalid for mvdan.cc/sh (POSIX-ish shell), such as unbalanced quotes, unsupported bashisms, or heredoc mistakes.

Common situations: Scripts using bash-4+ features the parser rejects; corrupted scripts from interrupted downloads; CRLF or encoding issues producing stray characters; scripts generated with template placeholders left unexpanded.

Related errors


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