Desk Clarity

Everyday document & browser help

Exists Is False but the Name Already Exists: Inspect a Broken Symlink Separately

By ON Lab · Sources checked November 8, 2026

A broken symbolic link still occupies a directory entry even when its target cannot be found. An existence check that follows the link can return false, while creating a directory at the same name raises FileExistsError. Check the entry and its target separately before deciding that the path is free.

Reproduce two different meanings of “exists”

A CPython user reported exactly this combination with os.path.exists and makedirs. Our Python 3.13.18/macOS fixture used four synthetic states: a regular file, a valid directory link, a link to a missing directory and an absent name.

from pathlib import Path
import os
import tempfile
with tempfile.TemporaryDirectory() as temporary:
    root = Path(temporary)
    (root / "actual").mkdir()
    (root / "valid").symlink_to(root / "actual", target_is_directory=True)
    (root / "broken").symlink_to(root / "gone", target_is_directory=True)
    link = root / "broken"
    print(link.exists())
    print(link.is_symlink())
    print(os.path.lexists(link))
    try:
        link.mkdir(exist_ok=True)
    except FileExistsError:
        print("the link name is already occupied")

The outputs were false, true, true, then the occupied-name message. The link’s missing target did not remove the link itself. exist_ok=True did not turn a broken link into the requested directory.

Inspect the entry without following it

Fixtureexists()is_symlink()lexists()
Regular fileTrueFalseTrue
Valid directory linkTrueTrueTrue
Broken directory linkFalseTrueTrue
Absent nameFalseFalseFalse

Current pathlib also offers a no-follow existence option. In our Python 3.13 fixture, link.exists(follow_symlinks=False) returned true for the broken link. Older interpreter interfaces may not accept that keyword; use the documentation for the running version rather than treating TypeError as a filesystem result.

Use stat and lstat for a more precise report

lstat inspects the link entry; stat normally follows it to the target. In our fixture, lstat obtained link metadata while following the broken target failed. Record the actual exception when permissions or an intermediate component may be involved. A false boolean alone does not diagnose every reason a target could not be inspected.

Before changing anything, record the exact link path and the target it stores. A relative target is interpreted relative to the link’s containing directory, not automatically the script’s current working directory. Moving a link without considering that relationship can change which target it names.

Do not automatically delete the occupied name

The next decision depends on ownership and purpose: should the original target be restored, should the link point somewhere else, or was this meant to be an ordinary directory? Those are different repairs. This guide deliberately performs no repair on a real path. Deleting a link just because exists returned false can destroy an intentional configuration or obscure the cause.

On shared or managed paths, preserve the evidence and resolve the intended layout before replacing the entry. A permission error should not be converted into a missing-path diagnosis. If another process can change the path concurrently, inspecting it first does not make a later create or replacement operation race-free.

Keep file-selection and creation policies separate

The glob selection guide addresses which names are discovered, not whether following a discovered link reaches a target. The exclusive-create guide addresses a different write boundary. Neither makes an occupied broken-link name disappear.

Official standard-library tools and the original report provide free alternatives. The added check here is a complete entry-versus-target matrix and an explicit refusal to infer that a false target-existence result authorizes replacement.

Method: Python 3.13 documentation, a historical CPython user report and four synthetic macOS path states. No user path was deleted or repaired. Windows link creation, permission failures, concurrent changes and every filesystem type were not reproduced.

Sources