{"record":{"id":"7a909950870f0c48","repo":"charmbracelet/crush","slug":"shell-run-cwd-is-required","errorCode":null,"errorMessage":"shell.Run: Cwd is required","messagePattern":"shell\\.Run: Cwd is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/shell/run.go","lineNumber":69,"sourceCode":"// Run parses and executes a shell command using the same mvdan.cc/sh\n// interpreter stack that the stateful [Shell] type uses (builtins,\n// optional block list, optional Go coreutils). It is safe to call\n// concurrently from multiple goroutines: each call builds its own\n// [interp.Runner] and shares no state with other callers or with any\n// [Shell] instance.\n//\n// Errors returned from the command itself (non-zero exit, context\n// cancellation, parse failures) follow the same conventions as\n// [Shell.Exec]: inspect with [IsInterrupt] and [ExitCode].\nfunc Run(ctx context.Context, opts RunOptions) (err error) {\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}()\n\n\tif opts.Cwd == \"\" {\n\t\treturn fmt.Errorf(\"shell.Run: Cwd is required\")\n\t}\n\n\tstdout := opts.Stdout\n\tif stdout == nil {\n\t\tstdout = io.Discard\n\t}\n\tstderr := opts.Stderr\n\tif stderr == nil {\n\t\tstderr = io.Discard\n\t}\n\n\tline, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), \"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not parse command: %w\", err)\n\t}\n\n\trunner, err := newRunner(opts.Cwd, opts.Env, opts.Stdin, stdout, stderr, opts.BlockFuncs)\n\tif err != nil {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/shell/run.go#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Always set Cwd in RunOptions, e.g. to an explicit directory or os.Getwd() result owned by the caller.","For temporary work, pass t.TempDir() (tests) or a dedicated scratch directory.","If a default is genuinely desired, resolve it at the call site rather than relying on Run.","Add a constructor or validation helper that fills Cwd so zero-value options cannot reach Run."],"exampleFix":"// before\nshell.Run(ctx, shell.RunOptions{Command: \"ls\"})\n// after\ncwd, _ := os.Getwd()\nshell.Run(ctx, shell.RunOptions{Command: \"ls\", Cwd: cwd})","handlingStrategy":"validation","validationCode":"if opts.Cwd == \"\" {\n    return fmt.Errorf(\"Cwd must be set before calling shell.Run\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if err.Error() == \"shell.Run: Cwd is required\" {\n        // programmer error: fill Cwd and retry once\n    }\n}","preventionTips":["Always set Cwd explicitly in RunOptions; never rely on zero values.","Use t.TempDir() in tests and explicit project dirs in production.","Add a helper that constructs RunOptions with a required cwd argument.","Fail fast in callers when cwd resolution fails instead of passing an empty string."],"tags":["shell","validation","api-misuse","required-parameter"],"backgroundTag":"missing-required-option","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}