charmbracelet/crush · error
cannot convert stop dir to absolute path: %w
Error message
cannot convert stop dir to absolute path: %w
What it means
When stopDir is non-empty, traverseUpBounded resolves it with filepath.Abs so the bounded walk can compare canonicalized paths. This error wraps a failure of that resolution, again only possible via os.Getwd when stopDir is relative.
Source
Thrown at internal/fsext/lookup.go:215
// traverseUp instead. If stopDir is set but is not an ancestor of dir
// the walk still stops at the filesystem root, so callers cannot
// accidentally produce an infinite walk by passing a sibling path.
//
// Boundary comparison is performed against symlink-resolved paths so
// that callers passing logically equivalent paths (a symlinked /var vs
// the underlying /private/var, for example) still terminate at the
// expected directory.
func traverseUpBounded(dir, stopDir string, walkFn func(dir string, owner int) error) error {
cwd, err := filepath.Abs(dir)
if err != nil {
return fmt.Errorf("cannot convert CWD to absolute path: %w", err)
}
stop := cwd
if stopDir != "" {
stop, err = filepath.Abs(stopDir)
if err != nil {
return fmt.Errorf("cannot convert stop dir to absolute path: %w", err)
}
}
canonStop := canonicalize(stop)
owner, err := Owner(dir)
if err != nil {
return fmt.Errorf("cannot get ownership: %w", err)
}
for {
err := walkFn(cwd, owner)
if err == nil || errors.Is(err, filepath.SkipDir) {
if canonicalize(cwd) == canonStop {
return nil
}
parent := filepath.Dir(cwd)
if parent == cwd {View on GitHub (pinned to 7944b8e522)
Solutions
- Pass an absolute stopDir (e.g. the resolved project root) to avoid Getwd dependency
- Ensure the process CWD is valid before the call, or chdir to a safe directory
- Pre-resolve the stop dir yourself with filepath.Abs and handle the error at a more meaningful layer
- Restart the process from a valid directory
Example fix
// before
fsext.LookupBounded(dir, "../../project", targets...)
// after
root, err := filepath.Abs("/workspaces/project")
if err != nil {
return err
}
fsext.LookupBounded(dir, root, targets...) Defensive patterns
Strategy: validation
Validate before calling
absStop, err := filepath.Abs(stopDir)
if err != nil {
return fmt.Errorf("stop dir %q not resolvable: %w", stopDir, err)
}
if _, err := os.Stat(absStop); err != nil {
return fmt.Errorf("stop dir unavailable: %w", err)
} Type guard
func stopDirResolvable(stopDir string) bool {
abs, err := filepath.Abs(stopDir)
return err == nil && func() bool { _, err := os.Stat(abs); return err == nil }()
} Try / catch
_, err := fsext.LookupBounded(dir, stop, targets...)
if err != nil {
if strings.Contains(err.Error(), "cannot convert stop dir") {
return fmt.Errorf("bad stop dir %q: %w", stop, err)
}
return err
} Prevention
- Resolve project roots to absolute paths once at startup and reuse them
- Never pass computed relative segments ("../..") directly as stopDir
- Keep the process CWD valid before any fsext calls
- Validate stopDir existence at configuration time
When it happens
Trigger: Calling LookupBounded/LookupClosestBounded with a relative stopDir (e.g. ".." or "./project") while the process working directory cannot be determined — deleted CWD, permission loss, or failed mount.
Common situations: Computing the project root as a relative path and passing it straight through; running from a removed build directory; containers with dangling CWD.
Related errors
- cannot convert CWD to absolute path: %w
- error resolving working directory: %w
- error resolving file path: %w
- failed to change directory: %v
- failed to get current working directory: %v
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/1c76542334b38ef3.
Report an issue: GitHub.