gastownhall/beads · error

validateExplicitPath sentinel (ErrNotFound/ErrNotExecutable)

validateExplicitPath sentinel (ErrNotFound/ErrNotExecutable)

Error message

%s=%q: %w

What it means

Resolve wraps a sentinel error (ErrNotFound or ErrNotExecutable) from validateExplicitPath when the explicitly pinned BEADS_DOLT_BIN value fails validation — the path does not exist, is not a regular file, or lacks an executable bit. Per the library's contract, an explicit override is an error rather than a silent fallthrough to sidecar or PATH, because an operator who pins a binary wants a hard failure, not a substituted one. The wrapped sentinel lets callers errors.Is() for a typed diagnosis.

Source

Thrown at internal/doltversion/resolve.go:123

// what gets returned and probed. This matters for a bare, separator-free
// value like BEADS_DOLT_BIN=dolt-next: os.Stat/exec on a bare name without
// a path separator resolve it two DIFFERENT ways — Go's os/exec (and the
// OS exec syscalls on Unix) treat a name with no separator as a PATH
// lookup, while os.Stat treats it as cwd-relative. Without absolutizing
// first, validateExplicitPath's os.Stat would silently validate a
// cwd-relative file while Probe's later exec.CommandContext(ctx, path,
// ...) would instead search PATH for a same-named binary — approving one
// binary and launching a different one. filepath.Abs forces both steps to
// agree on the same cwd-relative file in that case.
func Resolve(opts ResolveOptions) (string, Source, error) {
	if opts.EnvValue != "" {
		abs, err := filepath.Abs(opts.EnvValue)
		if err != nil {
			return "", SourceEnv, fmt.Errorf("%s=%q: resolving absolute path: %w", DoltBinEnvVar, opts.EnvValue, err)
		}
		abs = completeExecutableExt(abs)
		if err := validateExplicitPath(abs); err != nil {
			return "", SourceEnv, fmt.Errorf("%s=%q: %w", DoltBinEnvVar, opts.EnvValue, err)
		}
		return abs, SourceEnv, nil
	}

	if opts.SidecarValue != "" {
		abs, err := filepath.Abs(opts.SidecarValue)
		if err != nil {
			return "", SourceSidecar, fmt.Errorf("sidecar dolt binary %q: resolving absolute path: %w", opts.SidecarValue, err)
		}
		abs = completeExecutableExt(abs)
		if err := validateExplicitPath(abs); err != nil {
			return "", SourceSidecar, fmt.Errorf("sidecar dolt binary %q: %w", opts.SidecarValue, err)
		}
		return abs, SourceSidecar, nil
	}

	path, err := exec.LookPath("dolt")
	if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the path spelled in BEADS_DOLT_BIN actually exists (ls the exact value)
  2. Add the executable bit on Unix: chmod +x <path>
  3. On Windows, either spell the .exe extension or ensure the file exists so completeExecutableExt can complete it; if extensionless, remove it or name the .exe directly
  4. If the value was set as a temporary override, unset BEADS_DOLT_BIN to fall back to sidecar/PATH resolution
  5. errors.Is the returned error for doltversion.ErrNotFound vs ErrNotExecutable to distinguish missing vs unusable

Example fix

// before
export BEADS_DOLT_BIN=/opt/tools/dolt   # file deleted by upgrade
// after
export BEADS_DOLT_BIN=/opt/tools/dolt-0.32.1 && chmod +x /opt/tools/dolt-0.32.1
Defensive patterns

Strategy: validation

Validate before calling

if v := os.Getenv("BEADS_DOLT_BIN"); v != "" {
    abs, _ := filepath.Abs(v)
    if info, err := os.Stat(abs); err != nil || info.IsDir() || info.Mode()&0o111 == 0 {
        return fmt.Errorf("BEADS_DOLT_BIN=%q is not a runnable file", v)
    }
}

Type guard

func isRunnableFile(path string) bool {
    info, err := os.Stat(path)
    return err == nil && info.Mode().IsRegular() && info.Mode()&0o111 != 0
}

Prevention

When it happens

Trigger: Calling Resolve with ResolveOptions.EnvValue set to a path that fails validateExplicitPath: the file does not exist (ErrNotFound), resolves via symlink to nothing, is a directory or non-regular file, or has no executable bit on Unix (ErrNotExecutable).

Common situations: Typo'd BEADS_DOLT_BIN path; pointing at the extensionless name on Windows where only dolt.exe exists; pinning a binary that was later deleted or moved during an upgrade; chmod'ing a downloaded binary without +x; setting the var to a directory path.

Related errors


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