charmbracelet/crush · error

shell.Run: Cwd is required

Error message

shell.Run: Cwd is required

What it means

shell.Run requires a non-empty RunOptions.Cwd and refuses to silently fall back to the process working directory, because hooks and the bash tool have different notions of a default. Calling Run with the zero-value options (or any options without Cwd) returns this sentinel error before any parsing or execution.

Source

Thrown at internal/shell/run.go:69

// Run parses and executes a shell command using the same mvdan.cc/sh
// interpreter stack that the stateful [Shell] type uses (builtins,
// optional block list, optional Go coreutils). It is safe to call
// concurrently from multiple goroutines: each call builds its own
// [interp.Runner] and shares no state with other callers or with any
// [Shell] instance.
//
// Errors returned from the command itself (non-zero exit, context
// cancellation, parse failures) follow the same conventions as
// [Shell.Exec]: inspect with [IsInterrupt] and [ExitCode].
func Run(ctx context.Context, opts RunOptions) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("command execution panic: %v", r)
		}
	}()

	if opts.Cwd == "" {
		return fmt.Errorf("shell.Run: Cwd is required")
	}

	stdout := opts.Stdout
	if stdout == nil {
		stdout = io.Discard
	}
	stderr := opts.Stderr
	if stderr == nil {
		stderr = io.Discard
	}

	line, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
	if err != nil {
		return fmt.Errorf("could not parse command: %w", err)
	}

	runner, err := newRunner(opts.Cwd, opts.Env, opts.Stdin, stdout, stderr, opts.BlockFuncs)
	if err != nil {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Always set Cwd in RunOptions, e.g. to an explicit directory or os.Getwd() result owned by the caller.
  2. For temporary work, pass t.TempDir() (tests) or a dedicated scratch directory.
  3. If a default is genuinely desired, resolve it at the call site rather than relying on Run.
  4. Add a constructor or validation helper that fills Cwd so zero-value options cannot reach Run.

Example fix

// before
shell.Run(ctx, shell.RunOptions{Command: "ls"})
// after
cwd, _ := os.Getwd()
shell.Run(ctx, shell.RunOptions{Command: "ls", Cwd: cwd})
Defensive patterns

Strategy: validation

Validate before calling

if opts.Cwd == "" {
    return fmt.Errorf("Cwd must be set before calling shell.Run")
}

Try / catch

if err != nil {
    if err.Error() == "shell.Run: Cwd is required" {
        // programmer error: fill Cwd and retry once
    }
}

Prevention

When it happens

Trigger: Calling shell.Run(ctx, shell.RunOptions{}) or constructing RunOptions with Command set but Cwd left as "" — the exact path exercised by the register-builtin and command-blocking tests.

Common situations: Using Go's zero-value struct literal for quick scripts; building options dynamically where the cwd field is set conditionally and skipped; refactors that moved the cwd out of a shared options struct.

Related errors


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