{"record":{"id":"8b1195fecf4c1bbe","repo":"docker/compose","slug":"invalid-pid-d-only-positive-pids-are-allowed","errorCode":null,"errorMessage":"invalid PID (%d): only positive PIDs are allowed","messagePattern":"invalid PID \\((.+?)\\): only positive PIDs are allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pidfile/pidfile.go","lineNumber":57,"sourceCode":"\tif err != nil {\n\t\treturn 0, err\n\t}\n\tpid, err = strconv.Atoi(string(bytes.TrimSpace(pidByte)))\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\tif pid != 0 && alive(pid) {\n\t\treturn pid, nil\n\t}\n\treturn 0, nil\n}\n\n// Write writes a \"PID file\" at the specified path. It returns an error if the\n// file exists and contains a valid PID of a running process, or when failing\n// to write the file.\nfunc Write(path string, pid int) error {\n\tif pid < 1 {\n\t\treturn fmt.Errorf(\"invalid PID (%d): only positive PIDs are allowed\", pid)\n\t}\n\toldPID, err := Read(path)\n\tif err != nil && !os.IsNotExist(err) {\n\t\treturn err\n\t}\n\tif oldPID != 0 {\n\t\treturn fmt.Errorf(\"process with PID %d is still running\", oldPID)\n\t}\n\treturn os.WriteFile(path, []byte(strconv.Itoa(pid)), 0o644)\n}\n","sourceCodeStart":39,"sourceCodeEnd":68,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/pidfile/pidfile.go#L39-L68","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npid, _ := strconv.Atoi(cfg.PID) // err ignored; pid==0 on bad input\n_ = pidfile.Write(path, pid)\n\n// after\npid, err := strconv.Atoi(cfg.PID)\nif err != nil || pid < 1 {\n    return fmt.Errorf(\"invalid pid %q in config\", cfg.PID)\n}\nif err := pidfile.Write(path, pid); err != nil { return err }","handlingStrategy":"validation","validationCode":"if pid < 1 {\n    return fmt.Errorf(\"refusing to write pidfile: PID %d is not a real process\", pid)\n}\nerr := pidfile.Write(path, pid)","typeGuard":"func isValidPID(pid int) bool { return pid >= 1 }","tryCatchPattern":"if err := pidfile.Write(path, pid); err != nil {\n    if pid < 1 {\n        return fmt.Errorf(\"caller bug: invalid PID %d passed to pidfile.Write\", pid)\n    }\n    return err\n}","preventionTips":["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."],"tags":["pidfile","process","validation","go"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}