gastownhall/beads · error

ErrNotExecutable

ErrNotExecutable

Error message

%w: stat failed: %v

What it means

validateExplicitPath wraps a non-missing os.Lstat failure as ErrNotExecutable with the raw stat error preserved. A missing path is classified ErrNotFound; any other stat failure (most commonly EACCES on a parent directory) means the path may exist but the process cannot inspect it, so it is treated as a broken rather than absent candidate and reported as not executable. This check runs only for explicitly-configured binary paths (BEADS_DOLT_BIN env var or sidecar option), never for PATH lookup.

Source

Thrown at internal/doltversion/resolve.go:190

// validateExplicitPath checks an explicitly-named binary path (from env or
// sidecar, never from PATH — exec.LookPath already validates executability
// on the platforms that matter). It requires the path to exist, resolve to
// a regular file (following symlinks — a symlink to a valid binary is
// fine, a symlink to a directory or to nothing is not), and have at least
// one executable bit set.
func validateExplicitPath(path string) error {
	info, err := os.Lstat(path)
	if err != nil {
		// A missing path is genuinely ErrNotFound. Anything else (most
		// commonly EACCES on a parent directory) means the path may well
		// exist but this process can't tell — that is a "broken", not
		// "absent", candidate, so it maps to ErrNotExecutable with the raw
		// stat error preserved rather than being folded into the same
		// not-found bucket.
		if os.IsNotExist(err) {
			return fmt.Errorf("%w: %v", ErrNotFound, err)
		}
		return fmt.Errorf("%w: stat failed: %v", ErrNotExecutable, err)
	}

	realPath := path
	if info.Mode()&os.ModeSymlink != 0 {
		resolved, err := filepath.EvalSymlinks(path)
		if err != nil {
			return fmt.Errorf("%w: resolving symlink: %v", ErrNotFound, err)
		}
		realPath = resolved
		info, err = os.Stat(realPath)
		if err != nil {
			return fmt.Errorf("%w: %v", ErrNotFound, err)
		}
	}

	if info.IsDir() {
		return fmt.Errorf("%w: %s is a directory", ErrNotExecutable, realPath)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions on every directory component of the configured path (namei -l /path/to/dolt or ls -ld on each prefix) and grant the running user execute (search) permission on the parent directories.
  2. If running under systemd/container, verify the service User/Group can traverse the path; add the user to the owning group or use `o+x` on the directories.
  3. Read the wrapped `%v` stat error in the message — it names the exact errno (e.g. permission denied) and helps identify the failing component.
  4. Check for symlink loops (ELOOP) with `ls -la` along the path and fix the link chain.
  5. As a workaround, move or copy the dolt binary to a location accessible to the process and reconfigure BEADS_DOLT_BIN or the sidecar option.

Example fix

// before (service user cannot traverse /opt/private/dolt/dolt)
BEADS_DOLT_BIN=/opt/private/dolt/dolt  // -> ErrNotExecutable: stat failed: permission denied
// after
sudo chmod o+x /opt/private  // grant traverse permission on parent dirs
BEADS_DOLT_BIN=/opt/private/dolt/dolt  // resolves
Defensive patterns

Strategy: validation

Validate before calling

// Before configuring the explicit path, ensure every directory component is searchable
func pathStatOk(p string) error {
	dir := filepath.Dir(p)
	for dir != "/" && dir != "." {
		if _, err := os.Stat(dir); err != nil {
			return fmt.Errorf("cannot stat %s: %w", dir, err)
		}
		dir = filepath.Dir(dir)
	}
	if _, err := os.Stat(p); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("stat failed on %s: %w", p, err)
	}
	return nil
}
// if err := pathStatOk(binPath); err != nil { fall back to PATH lookup }

Type guard

func statAccessible(p string) bool {
	_, err := os.Stat(p)
	return err == nil
}

Try / catch

path, err := resolver.Resolve(ctx)
var notExec *ErrNotExecutable
if errors.As(err, &notExec) {
	log.Warnf("explicit dolt binary unusable (%v); falling back to PATH", err)
	path, err = exec.LookPath("dolt")
}
if err != nil {
	return fmt.Errorf("no dolt binary available: %w", err)
}

Prevention

When it happens

Trigger: Calling Resolve, Probe, or fingerprintMatches with an explicit dolt binary path (BEADS_DOLT_BIN env or Sidecar option) where os.Lstat fails with an error other than not-found — typically EACCES on a parent directory in the path, or a dangling error such as ELOOP on a symlink cycle.

Common situations: A restrictive (mode 0700 root-owned) directory sits in the middle of the configured path, so a non-root process cannot stat through it; the path traverses a symlink loop; the binary lives under a directory the service user lacks search permission on after a permissions hardening change.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/ad9c051fa5e0cac6. Report an issue: GitHub.