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
- 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.
- 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.
- Read the wrapped `%v` stat error in the message — it names the exact errno (e.g. permission denied) and helps identify the failing component.
- Check for symlink loops (ELOOP) with `ls -la` along the path and fix the link chain.
- 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, ¬Exec) {
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
- Run as a user that can traverse every directory on the explicit binary path (check with `namei -l`).
- Keep dolt binaries in standard locations like /usr/local/bin with world-execute on parent dirs.
- After hardening directory permissions, re-test service accounts that resolve explicit binary paths.
- Log the wrapped stat errno from the error message to quickly identify the failing path component.
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
- dolt path is not executable
- failed to create backup directory: %w
- failed to create temp file: %w
- failed to create backup file: %w
- %d artifact(s) could not be removed
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/ad9c051fa5e0cac6.
Report an issue: GitHub.