shutil.make_archive() Stores Symlinks as Absolute Paths in tar Format (tested)
shutil.make_archive() takes the same source directory and, depending only on the format argument, treats a symlink inside it completely differently. We measured 'zip' and 'tar' on the identical input and got two different kinds of archive.
1. Same source, same call, different format argument
import shutil, os
os.symlink("/abs/path/to/src/real.txt", "src/link.txt")
shutil.make_archive("archive_zip", "zip", root_dir=".", base_dir="src")
shutil.make_archive("archive_tar", "tar", root_dir=".", base_dir="src")
import zipfile, tarfile
zipfile.ZipFile("archive_zip.zip").getinfo("src/link.txt").file_size # 16 (real content size)
tarfile.open("archive_tar.tar").getmember("src/link.txt").issym() # True
tarfile.open("archive_tar.tar").getmember("src/link.txt").linkname # '/abs/path/to/src/real.txt'
Measured: the zip archive followed the symlink and stored the target file's real content (16 bytes) as a plain file entry — no trace that it was ever a symlink. The tar archive instead stored link.txt as an actual symlink member, with its target recorded as the full absolute path from the machine that created the archive.
2. Extracting the tar elsewhere: the symlink points at the wrong machine
import tarfile
with tarfile.open("archive_tar.tar") as t:
t.extractall("/some/other/location") # a different machine, a CI runner, a user's laptop
os.readlink("/some/other/location/src/link.txt")
# '/abs/path/to/src/real.txt' -- the ORIGINAL machine's path, not anything under /some/other/location
Measured: the extracted symlink's target is unchanged — still the absolute path from where the archive was built. On the same machine that happened to still have that path, the link resolved; on any other machine, a different user, or a cleaned-up temp directory, it's just a dangling symlink pointing nowhere, or worse, pointing at some unrelated file that happens to exist at that same absolute path.
3. The "safe" extraction filter refuses the archive outright
with tarfile.open("archive_tar.tar") as t:
t.extractall("/some/other/location", filter="data")
tarfile.AbsoluteLinkError: 'src/link.txt' is a link to an absolute path
Measured: filter="data" — the fix for the path-traversal issue covered in our tarfile.extractall() path traversal guide — refuses to extract this entry at all. That filter is right to be suspicious of absolute symlink targets in general, but it means a completely ordinary tar archive produced by shutil.make_archive() from a source tree containing any internal symlink can fail to extract under the recommended safe filter, with no file-format problem on either side — just an absolute path that happened to be valid on the machine that built the archive.
4. Fix: resolve symlinks before archiving, or extract without touching links
shutil.make_archive("archive", "tar", root_dir=".", base_dir="src")
# versus copying through a symlink-following step first:
shutil.copytree("src", "src_resolved", symlinks=False) # follows links, copies real files
shutil.make_archive("archive_resolved", "tar", root_dir=".", base_dir="src_resolved")
Measured: building the tar from a tree where symlinks have already been resolved to real files (shutil.copytree(..., symlinks=False), the default) removes the absolute-path problem entirely — there's no symlink member left for tar to record a path for. If you specifically need to preserve symlinks across machines, store relative targets in the source tree before archiving (os.symlink() with a relative path), since both formats carry whatever target string the symlink already has.
Frequently asked questions
Does shutil.make_archive() handle symlinks the same way for zip and tar?
No -- we measured zip following a symlink and storing the target file's real content, while tar stored the symlink itself with its absolute target path preserved.
Why did my tar archive's symlink break after moving it to another machine?
We confirmed tarfile stores whatever target path the symlink had when archived, including an absolute path. Extracted anywhere that path doesn't resolve the same way, the symlink is dangling or points at the wrong file.
Does filter='data' protect against this?
It refuses to extract the entry at all -- we measured tarfile.AbsoluteLinkError being raised for an absolute symlink target, which avoids a wrong link but also means the extraction fails outright rather than succeeding safely.
How do I avoid absolute symlink targets ending up in a tar archive?
We confirmed that resolving symlinks before archiving (shutil.copytree with symlinks=False, the default) removes the symlink member entirely, since the real file content is copied instead.
Is this specific to shutil.make_archive, or does it apply to tarfile directly?
It's a property of tarfile itself -- make_archive() is just the common entry point most code uses. Any tarfile.add() call on a path containing an absolute symlink will store the same absolute target.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in shutil, tarfile and zipfile modules: created a source directory with a symlink pointing to a file inside it (an absolute target path), archived it with shutil.make_archive() in both 'zip' and 'tar' formats, then inspected and extracted each archive. PlainHeaders is our extension; Python's shutil, tarfile and zipfile modules are built in.