Go Exec Executable File Not Found in PATH Error Fully
Fix the Go error exec executable file not found in PATH: why exec.Command needs the program on PATH, LookPath, absolute paths, containers and services.

A Go program that shells out works on your laptop and prints exec: "git": executable file not found in $PATH in production. The program may be installed on the machine, but the process cannot see it. The mismatch comes from the process environment, the same family of problem as scripts that fail in cron. The commands assume Go 1.23.
Reproduce and check
package main
import (
"errors"
"fmt"
"os"
"os/exec"
)
func main() {
path, err := exec.LookPath("git")
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
fmt.Fprintln(os.Stderr, "git is not installed or not on PATH:", os.Getenv("PATH"))
os.Exit(127)
}
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("using", path)
out, err := exec.Command(path, "--version").Output()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Print(string(out))
}git is not installed or not on PATH: /usr/local/bin
Printing the process's own PATH in the error is the quickest way to see the cause. LookPath does exactly what exec.Command does internally, so you can fail early with a clear message. Exit code 127 is the shell convention for "command not found", as in exit codes 126 and 127.
The usual causes
container: FROM scratch no git, no sh, no PATH
container: FROM gcr.io/distroless/static binaries only what you copy
systemd: Environment=PATH is minimal
cron: PATH=/usr/bin:/bin
sudo: secure_path overrides yours
Each is an environment that has fewer tools or a shorter PATH than your shell. In a multi-stage Docker build, a binary that calls git or curl needs those installed in the final image, not just the builder; see static binaries in Docker. Alpine images ship BusyBox commands with fewer options than GNU ones, so a call that works on Debian can fail there for a different reason.
Fixes
cmd := exec.Command("/usr/bin/git", "status")
cmd.Env = append(os.Environ(), "PATH=/usr/local/bin:/usr/bin:/bin")
cmd.Dir = "/srv/repo"
out, err := cmd.CombinedOutput()Use an absolute path for a tool whose location you know, or extend cmd.Env for the child. Set PATH in the systemd unit with Environment=PATH=... or install the tool in a standard directory. Ask the question at design time: a dependency on an external program is a deployment requirement, so list it in the documentation and check for it at start-up.
Relative paths and shell features
cmd := exec.Command("./tool") // relative to the working directory, not PATH
cmd2 := exec.Command("sh", "-c", "cd /tmp && ls | head")Since Go 1.19, exec.Command refuses to run a program found in the current directory through PATH lookup and returns exec.ErrDot, a security fix. Pass an explicit ./tool path if that is what you mean. Shell syntax such as pipes, redirects, cd and globs is not interpreted by exec.Command; use sh -c only with trusted input, because it opens a command injection path. More in os/exec basics.
How Go finds a program
The error exec: "name": executable file not found in $PATH comes from exec.LookPath, which exec.Command calls when the name has no path separator. LookPath reads the PATH environment variable of the running process, splits it on colons, and checks each directory for an executable regular file with that name. If none matches, it fails. Importantly, it uses the process's environment, not the environment of the shell you were using when you tested the command, which is why a program that runs fine in a terminal can fail when launched by a service manager or a container with a different environment.
Since Go 1.19, LookPath refuses to return a result found relative to the current directory, which closes a class of attacks in which a malicious executable placed in the working directory shadows a system tool. If a program only exists as ./tool, the call needs an explicit path with a separator.
What the process actually sees
To debug, print the facts at the point of failure. Log the value of os.Getenv("PATH"), the current working directory and the user the process runs as. Call exec.LookPath directly and print its error, since it is the same one. In a container, run a shell in the same image with the same user and try command -v name there. Minimal images such as scratch and some distroless variants contain no shell and few tools, so a program that shells out to git or sh must have them copied in.
Static binaries make this worse in an instructive way. The Go program may be fine, while the thing it executes is not in the final image, because a multi-stage build copied only the binary. Add the required tools to the final stage, or replace the external call with a library when practical.
Setting PATH deliberately
A dependable program does not depend on the ambient PATH for anything important. Options include resolving the tool at startup and failing early with a clear message, accepting an absolute path through a flag or environment variable, and setting cmd.Env explicitly for the child process. If you assign cmd.Env, remember that it replaces the whole environment, so copy the existing variables with os.Environ() and then append or override the ones you need. Forgetting this removes HOME and PATH from the child and causes a second round of confusing errors.
On Windows, the lookup also considers PATHEXT, so exec.Command("tool") can find tool.exe or tool.cmd. Programs that shell out need a check on every platform they claim to support, because the extension rules, quoting and available tools all differ.
Error handling worth adding
Wrap the failure with context. Returning fmt.Errorf("find git: %w", err) lets callers check with errors.Is(err, exec.ErrNotFound) and show a helpful message such as "install git or set GIT_PATH". For optional tools, check for them once at startup and disable the related features with a notice, instead of failing in the middle of a long run. Finally, treat the lookup as a security boundary: never construct a command name from user input, and prefer absolute paths for anything privileged, so a changed PATH cannot redirect the program to a different binary.
In tests, keep the dependency on external tools injectable. Define a variable or an interface for the lookup so a test can substitute a fake executable placed in a temporary directory at the front of PATH. That makes the not-found path testable without uninstalling anything, and proves that the error message tells the user what to do.
Document required external programs in the README next to the install instructions. A tool that shells out to git, rsync or ssh is not self-contained, and users on minimal systems should learn that from the documentation and not from a stack trace.
Log the resolved path when the lookup succeeds, at least at debug level. A line that says which binary was chosen settles later arguments about whether the right version ran, particularly on hosts that carry two installations of the same tool in different directories. If the program supports a configuration option for the path, print its source too, so an operator can tell a value from the environment from a built-in default.
Where this does not apply
If the error names a program that is present and executable, check for a missing interpreter in its shebang line, which produces "no such file or directory" instead. A wrong architecture binary shows exec format error. See the os/exec package documentation and the Go blog on PATH security.


