pytest-dev/pytest · error · OSError
The temporary directory {rootdir} is a symbolic link. Fix th
Error message
The temporary directory {rootdir} is a symbolic link. Fix this and try again. What it means
Raised by TempPathFactory.getbasetemp as an OSError when the auto-created basetemp root directory (typically /tmp/pytest-of-<user>) is itself a symbolic link. pytest deliberately refuses to follow a symlink here to avoid a TOCTOU symlink-swapping attack where another user redirects the shared temp root to an attacker-controlled location. The stat is taken without following symlinks where the platform allows it.
Source
Thrown at src/_pytest/tmpdir.py:189
# temproot is usually shared).
# Also, to keep things private, fixup any world-readable temp
# rootdir's permissions. Historically 0o755 was used, so we can't
# just error out on this, at least for a while.
# Don't follow symlinks, otherwise we're open to symlink-swapping
# TOCTOU vulnerability.
# This check makes us vulnerable to a DoS - a user can `mkdir
# /tmp/pytest-of-otheruser` and then `otheruser` will fail this
# check. For now we don't consider it a real problem. otheruser can
# change their TMPDIR or --basetemp, and maybe give the prankster a
# good scolding.
uid = get_user_id()
if uid is not None:
stat_follow_symlinks = (
False if os.stat in os.supports_follow_symlinks else True
)
rootdir_stat = rootdir.stat(follow_symlinks=stat_follow_symlinks)
if stat.S_ISLNK(rootdir_stat.st_mode):
raise OSError(
f"The temporary directory {rootdir} is a symbolic link. "
"Fix this and try again."
)
if rootdir_stat.st_uid != uid:
raise OSError(
f"The temporary directory {rootdir} is not owned by the current user. "
"Fix this and try again."
)
if (rootdir_stat.st_mode & 0o077) != 0:
chmod_follow_symlinks = (
False if os.chmod in os.supports_follow_symlinks else True
)
rootdir.chmod(
rootdir_stat.st_mode & ~0o077,
follow_symlinks=chmod_follow_symlinks,
)
keep = self._retention_count
if self._retention_policy == "none":View on GitHub (pinned to 98b357f69e)
Solutions
- Remove the symlink: `rm /tmp/pytest-of-<user>` and let pytest recreate it as a real directory.
- Set a safe basetemp explicitly via `pytest --basetemp=/real/dir` or TMPDIR env var pointing at a non-symlink directory.
- If the OS layout forces a symlink at /tmp, point TMPDIR to a real directory on a non-linkified filesystem.
- Audit setup scripts and provisioning that may `ln -s` into /tmp.
Example fix
// before # /tmp/pytest-of-alice -> /mnt/big/pytest-alice (symlink) $ pytest tests/ // after $ rm /tmp/pytest-of-alice $ TMPDIR=/mnt/big/realtmp pytest tests/ # or $ pytest --basetemp=/mnt/big/realtmp/pytest-alice tests/
Defensive patterns
Strategy: validation
Validate before calling
import os, pathlib
def ensure_basetemp_not_symlink(p: pathlib.Path):
if p.is_symlink():
raise OSError(f"{p} is a symlink; refusing to use as basetemp")
return p Type guard
import os
def is_real_dir(p) -> bool:
return os.path.isdir(p) and not os.path.islink(p) Prevention
- Always pass --basetemp to a real directory in CI instead of relying on /tmp layout.
- Provision TMPDIR to a non-symlinked filesystem in container images.
- Document the basetemp requirement in the project README for shared runners.
When it happens
Trigger: Someone (a human, a setup script, or another tool) created /tmp/pytest-of-<user> as a symlink to elsewhere, then pytest tries to initialize its basetemp under it. Triggered on the first test/fixture that needs tmp_path or any call to tmp_path_factory.getbasetemp().
Common situations: Shared CI runners where /tmp is symlinked (e.g. to a larger disk); a previous `ln -s` for debugging; Docker/k8s volume mounts that manifest as symlinks; misconfigured TMPDIR pointing at a symlinked dir.
Related errors
- The temporary directory {rootdir} is not owned by the curren
- {basename} is not a normalized and relative path
- {optname} must be a filename, given: {path}
- {optname} must be a directory, given: {path}
- name is not allowed to contain path separators
AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04).
Data as JSON: /data/errors/d01c0c2d9c824590.json.
Report an issue: GitHub.