docker/compose · error
invalid PID (%d): only positive PIDs are allowed
Error message
invalid PID (%d): only positive PIDs are allowed
What it means
pidfile.Write refuses PIDs below 1: PID 0 has no meaningful process identity (on Unix it is the swapper; kill(0) would signal the whole process group) and negative PIDs are invalid. The check fires before any file I/O, so no pidfile is created or clobbered.
Source
Thrown at internal/pidfile/pidfile.go:57
if err != nil {
return 0, err
}
pid, err = strconv.Atoi(string(bytes.TrimSpace(pidByte)))
if err != nil {
return 0, nil
}
if pid != 0 && alive(pid) {
return pid, nil
}
return 0, nil
}
// Write writes a "PID file" at the specified path. It returns an error if the
// file exists and contains a valid PID of a running process, or when failing
// to write the file.
func Write(path string, pid int) error {
if pid < 1 {
return fmt.Errorf("invalid PID (%d): only positive PIDs are allowed", pid)
}
oldPID, err := Read(path)
if err != nil && !os.IsNotExist(err) {
return err
}
if oldPID != 0 {
return fmt.Errorf("process with PID %d is still running", oldPID)
}
return os.WriteFile(path, []byte(strconv.Itoa(pid)), 0o644)
}
View on GitHub (pinned to ddc4b044b6)
Solutions
- Pass a real process ID, normally os.Getpid(), at the point the process starts.
- Validate the source of the PID (config parsing) and fail early on 0/-1 instead of forwarding it.
- If the PID comes from parsing, check the strconv error rather than using the zero result.
- Unit-test the writer with pid 0 and -1 to lock in the contract.
Example fix
// before
pid, _ := strconv.Atoi(cfg.PID) // err ignored; pid==0 on bad input
_ = pidfile.Write(path, pid)
// after
pid, err := strconv.Atoi(cfg.PID)
if err != nil || pid < 1 {
return fmt.Errorf("invalid pid %q in config", cfg.PID)
}
if err := pidfile.Write(path, pid); err != nil { return err } Defensive patterns
Strategy: validation
Validate before calling
if pid < 1 {
return fmt.Errorf("refusing to write pidfile: PID %d is not a real process", pid)
}
err := pidfile.Write(path, pid) Type guard
func isValidPID(pid int) bool { return pid >= 1 } Try / catch
if err := pidfile.Write(path, pid); err != nil {
if pid < 1 {
return fmt.Errorf("caller bug: invalid PID %d passed to pidfile.Write", pid)
}
return err
} Prevention
- Pass os.Getpid() at startup, not a parsed config value.
- Check strconv.Atoi errors instead of forwarding zero values.
- Lock the contract with a unit test for pid 0 and -1.
When it happens
Trigger: Calling pidfile.Write(path, pid) with pid <= 0 — commonly pid taken from a config/env value parsed to 0 on failure, a zero-value struct field never set, or -1 used as a sentinel.
Common situations: Passing os.Getpid() results from a forked child before it re-reads its PID; defaults like 0 in YAML configs; strconv.Atoi errors swallowed and the zero value forwarded; placeholder -1 sentinels reaching the writer.
Related errors
- process with PID %d is still running
- unsupported protocol for address: %s
- unsupported network: %s
- socket address is too long: %s
- invalid digest %s: %w
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/8b1195fecf4c1bbe.
Report an issue: GitHub.