pathlib Path.exists() Returns False for a Dangling Symlink (tested)
A symlink can exist on disk while pointing at a target that doesn't. We measured what pathlib.Path.exists() reports for exactly that case: False — even though the symlink itself is a real directory entry.
1. A symlink pointing at nothing
from pathlib import Path
import os
p = Path("/tmp/dangling_link")
p.symlink_to("/tmp/nonexistent_target")
print("is_symlink:", p.is_symlink())
print("exists:", p.exists())
print("os.path.lexists:", os.path.lexists(p))
print("in os.listdir:", p.name in os.listdir("/tmp"))
is_symlink: True
exists: False
os.path.lexists: True
in os.listdir: True
Measured: the symlink is real — is_symlink() is True, os.path.lexists() (which doesn't follow links) is True, and it shows up in a plain directory listing. But .exists() follows the link to check the target, finds nothing there, and reports False — no exception, no distinction in the return value between "nothing here" and "a broken link is here."
2. Where this silently breaks code
def process_if_present(path):
if path.exists():
return path.stat().st_size
return None # intended for "nothing to do here"
print(process_if_present(p))
None
Measured: a dangling symlink is treated identically to "this path doesn't exist at all," silently taking the same branch as a genuinely absent file. A cleanup script written as for f in dir.iterdir(): if f.exists(): keep(f) will skip every broken symlink without logging anything, leaving them behind indefinitely instead of flagging or removing them.
3. Fix: check is_symlink() separately, or use lexists
if p.is_symlink() and not p.exists():
print(p, "is a broken symlink")
# p.unlink() to remove it, or re-point it, depending on intent
/tmp/dangling_link is a broken symlink
Measured: checking is_symlink() alongside exists() distinguishes "never existed" from "exists as a link, but the target is gone" — the two cases .exists() alone can't tell apart. p.unlink() removes the dangling link itself (it does not need to follow the link to work, unlike .exists()).
Frequently asked questions
Does Path.exists() follow symlinks?
Yes -- we measured it following the link and checking whether the target exists, not whether the symlink itself is present. A dangling symlink's target check comes back False.
How do I detect a symlink whose target is missing?
We confirmed path.is_symlink() and not path.exists() together identify a dangling symlink -- is_symlink() doesn't follow the link, so it stays True even when the target is gone.
Does a dangling symlink raise an exception when checked?
Not with .exists() or .is_symlink() -- both return plain booleans silently. We confirmed .stat() (which follows the link) is what raises FileNotFoundError.
Will a cleanup loop based on path.exists() find broken symlinks?
We measured a dangling symlink being treated the same as a nonexistent path by a .exists() check, so a loop that only acts when .exists() is True will silently skip every broken symlink without removing or flagging it.
Is there a version of exists() that doesn't follow symlinks?
os.path.lexists() is the equivalent that checks the link itself rather than its target -- we confirmed it returns True for a dangling symlink where Path.exists() returns False.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in pathlib and os modules: created a symlink pointing at a nonexistent target, then checked Path.exists(), Path.is_symlink(), os.path.lexists(), Path.stat(), and whether os.listdir() reports the entry. PlainHeaders is our extension; Python's pathlib and os modules are built in.