Simplified code
After some while thinking, I realized you can simplify your original function to:
(defun find-dir-upwards (dir marker-file)
(let ((parent (uiop:pathname-parent-directory-pathname dir)))
(unless (equal dir parent)
(if (probe-file (merge-pathnames marker-file dir))
dir
(find-dir-upwards parent marker-file)))))
And its correspoding loop function is:
(defun find-dir-upwards (dir marker-file)
(loop for parent = (uiop:pathname-parent-directory-pathname dir)
unless (equal dir parent)
do (if (probe-file (merge-pathnames marker-file dir))
(return dir)
(setf dir parent))))
As @Rezo pointed out in this post, probe-file
is portable between different Common Lisp implementations, while uiop:file-exists-p
is not.
Original answer
I would define the loop differently.
Use the (let ((dir dir)) (loop ... (setf dir (<some updater function>))))
idiom to have some reference variable(s) which are updated every run through the loop - until the condition is met.
(defun find-dir-upwards (dir marker-file)
(let ((f (merge-pathnames marker-file dir))
(dir dir))
(loop for parent = (uiop:pathname-parent-directory-pathname dir)
unless (equal dir parent)
do (if (uiop:file-exists-p f) ;; if marker-file in dir
(return dir) ;; then arrived at destination
(progn
(setf dir parent) ;; otherwise climb up one dir-level
(setf f (merge-pathnames marker-file dir)))))))
Which is then further dissectable to:
(defun marker-file-in-dir-p (dir marker-file)
(uiop:file-exists-p (merge-pathnames marker-file dir)))
(defun find-dir-upwards (dir marker-file)
(let ((dir dir))
(loop for parent = (uiop:pathname-parent-directory-pathname dir) ;; parent from dir
unless (equal dir parent)
do (if (marker-file-in-dir-p dir marker-file)
(return dir)
(setf dir parent)))))