{"record":{"id":"ad9c051fa5e0cac6","repo":"gastownhall/beads","slug":"errnotexecutable-ad9c05","errorCode":"ErrNotExecutable","errorMessage":"%w: stat failed: %v","messagePattern":"%w: stat failed: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/doltversion/resolve.go","lineNumber":190,"sourceCode":"// validateExplicitPath checks an explicitly-named binary path (from env or\n// sidecar, never from PATH — exec.LookPath already validates executability\n// on the platforms that matter). It requires the path to exist, resolve to\n// a regular file (following symlinks — a symlink to a valid binary is\n// fine, a symlink to a directory or to nothing is not), and have at least\n// one executable bit set.\nfunc validateExplicitPath(path string) error {\n\tinfo, err := os.Lstat(path)\n\tif err != nil {\n\t\t// A missing path is genuinely ErrNotFound. Anything else (most\n\t\t// commonly EACCES on a parent directory) means the path may well\n\t\t// exist but this process can't tell — that is a \"broken\", not\n\t\t// \"absent\", candidate, so it maps to ErrNotExecutable with the raw\n\t\t// stat error preserved rather than being folded into the same\n\t\t// not-found bucket.\n\t\tif os.IsNotExist(err) {\n\t\t\treturn fmt.Errorf(\"%w: %v\", ErrNotFound, err)\n\t\t}\n\t\treturn fmt.Errorf(\"%w: stat failed: %v\", ErrNotExecutable, err)\n\t}\n\n\trealPath := path\n\tif info.Mode()&os.ModeSymlink != 0 {\n\t\tresolved, err := filepath.EvalSymlinks(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: resolving symlink: %v\", ErrNotFound, err)\n\t\t}\n\t\trealPath = resolved\n\t\tinfo, err = os.Stat(realPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: %v\", ErrNotFound, err)\n\t\t}\n\t}\n\n\tif info.IsDir() {\n\t\treturn fmt.Errorf(\"%w: %s is a directory\", ErrNotExecutable, realPath)\n\t}","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/doltversion/resolve.go#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (service user cannot traverse /opt/private/dolt/dolt)\nBEADS_DOLT_BIN=/opt/private/dolt/dolt  // -> ErrNotExecutable: stat failed: permission denied\n// after\nsudo chmod o+x /opt/private  // grant traverse permission on parent dirs\nBEADS_DOLT_BIN=/opt/private/dolt/dolt  // resolves","handlingStrategy":"validation","validationCode":"// Before configuring the explicit path, ensure every directory component is searchable\nfunc pathStatOk(p string) error {\n\tdir := filepath.Dir(p)\n\tfor dir != \"/\" && dir != \".\" {\n\t\tif _, err := os.Stat(dir); err != nil {\n\t\t\treturn fmt.Errorf(\"cannot stat %s: %w\", dir, err)\n\t\t}\n\t\tdir = filepath.Dir(dir)\n\t}\n\tif _, err := os.Stat(p); err != nil && !os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"stat failed on %s: %w\", p, err)\n\t}\n\treturn nil\n}\n// if err := pathStatOk(binPath); err != nil { fall back to PATH lookup }","typeGuard":"func statAccessible(p string) bool {\n\t_, err := os.Stat(p)\n\treturn err == nil\n}","tryCatchPattern":"path, err := resolver.Resolve(ctx)\nvar notExec *ErrNotExecutable\nif errors.As(err, &notExec) {\n\tlog.Warnf(\"explicit dolt binary unusable (%v); falling back to PATH\", err)\n\tpath, err = exec.LookPath(\"dolt\")\n}\nif err != nil {\n\treturn fmt.Errorf(\"no dolt binary available: %w\", err)\n}","preventionTips":["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."],"tags":["filesystem","permissions","go","binary-resolution"],"backgroundTag":"stat-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}