OSError: [Errno 18] Invalid cross-device link in Python (tested fix)
OSError: [Errno 18] Invalid cross-device link means os.rename (or os.replace, Path.rename) tried to move a file to a different filesystem, such as from /tmp to your disk or into a Docker volume. We reproduced it and tested the fixes.
1. Cause: rename() cannot cross filesystems
import os, errno
src = "/tmp/e18w/report.txt" # /tmp is a tmpfs here
dst = "report.txt" # current dir, on the disk
open(src, "w").write("hello\n")
try:
os.rename(src, dst)
except OSError as e:
print(f"{type(e).__name__}: {e}")
print("errno:", e.errno, errno.errorcode[e.errno])
Output:
OSError: [Errno 18] Invalid cross-device link: '/tmp/e18w/report.txt' -> 'report.txt'
errno: 18 EXDEV
os.rename is the rename(2) system call, which only relabels a file inside one filesystem. It never copies data, so when the source and destination are on different filesystems the kernel refuses with EXDEV. On this machine /tmp is a tmpfs (in RAM) and the current directory is on the disk, which is the most common way to hit it. Others: Docker volumes and bind mounts, a separate /home partition, USB drives, network shares. The Linux man page notes that rename does not work across different mount points even if the same filesystem is mounted on both, so two bind mounts of one disk still fail.
2. os.replace, pathlib and os.link fail the same way
import os
from pathlib import Path
src = Path("/tmp/e18w/report.txt")
dst = Path("report.txt") # current dir, on the disk
src.write_text("hello\n")
for name, fn in [("os.replace", os.replace), ("Path.rename", Path.rename),
("Path.replace", Path.replace), ("os.link", os.link)]:
try:
fn(src, dst)
except OSError as e:
print(f"{name:12} -> {type(e).__name__}: [Errno {e.errno}] {e.strerror}")
print("same device?", os.stat(src).st_dev == os.stat(dst.resolve().parent).st_dev)
Output:
os.replace -> OSError: [Errno 18] Invalid cross-device link
Path.rename -> OSError: [Errno 18] Invalid cross-device link
Path.replace -> OSError: [Errno 18] Invalid cross-device link
os.link -> OSError: [Errno 18] Invalid cross-device link
same device? False
All four are thin wrappers over a same-filesystem call, so switching from os.rename to os.replace or Path.rename does not help. Comparing os.stat(...).st_dev tells you in advance whether two paths are on the same device.
3. Fix: shutil.move copies when rename fails
import os, shutil
from pathlib import Path
src = Path("/tmp/e18w/report.txt")
dst = Path("report.txt") # current dir, on the disk
src.write_text("hello\n")
print(shutil.move(src, dst))
print("src exists:", src.exists(), "| dst:", dst.read_text().strip())
dst.unlink()
Output:
report.txt
src exists: False | dst: hello
shutil.move tries os.rename first and, when that fails, copies the file (with copy2, keeping metadata) and deletes the source. That works across filesystems, but it is no longer atomic: if the process dies mid-copy you can be left with a partial destination file.
4. Fix for atomic writes: create the temp file next to the target
import os, tempfile
from pathlib import Path
def atomic_write(path, data):
path = Path(path)
# temp file in the SAME directory, so os.replace never crosses a filesystem
fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=path.name + ".")
try:
with os.fdopen(fd, "w") as f:
f.write(data)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, path)
except BaseException:
os.unlink(tmp)
raise
target = Path("config.json")
atomic_write(target, '{"ok": true}\n')
print(target.read_text().strip())
target.unlink()
Output:
{"ok": true}
The usual way to trigger Errno 18 is the "write to a temp file, then rename over the real file" pattern, with the temp file created by tempfile in its default directory (/tmp). Pass dir= the target's own directory so both files are on the same filesystem; then os.replace is atomic and cannot raise EXDEV.
Related: Python errno codes list · shutil copy/move PermissionError · Errno 17 file exists.
Frequently asked questions
What does Errno 18 Invalid cross-device link mean?
The source and destination are on different filesystems (or mount points), and rename only works within one. The errno name is EXDEV.
Does os.replace fix Invalid cross-device link?
No. os.replace, Path.rename, Path.replace and os.link all raise the same Errno 18. Use shutil.move, which copies and then deletes.
How do I do an atomic write without Errno 18?
Create the temp file in the destination directory with tempfile.mkstemp(dir=target_dir), write and fsync it, then os.replace it over the target.
Written with AI assistance. Measured on 2026-10-22 with Python 3.13.5 on Debian, moving files from /tmp (tmpfs) to a directory on the disk with os.rename, os.replace, Path.rename, Path.replace, os.link and shutil.move, plus an atomic-write helper. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.