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

  1. Pass a real process ID, normally os.Getpid(), at the point the process starts.
  2. Validate the source of the PID (config parsing) and fail early on 0/-1 instead of forwarding it.
  3. If the PID comes from parsing, check the strconv error rather than using the zero result.
  4. 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

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


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/8b1195fecf4c1bbe. Report an issue: GitHub.