unslothai/unsloth · warning · RuntimeError
Linked source changed while it was copied
Error message
Linked source changed while it was copied
What it means
After _copy_exact finishes, _snapshot fstats the still-open source descriptor again and compares (size, mtime_ns, dev, ino) to the pre-copy values. Any drift means the file was modified while it was being read, so the completed snapshot could be torn and is deleted via _remove_snapshot before the error propagates.
Source
Thrown at studio/backend/core/rag/folder_sync.py:1052
metadata["inode"],
)
actual = (before.st_size, before.st_mtime_ns, before.st_dev, before.st_ino)
# Only an identity os.fstat can reproduce is comparable here: os.scandir reports none at all
# on Windows, and the os.lstat _scan falls back to disagrees with os.fstat on file systems
# without stable file ids. Size and mtime still gate those, and the post-copy check below is
# fstat to fstat either way.
usable = metadata["inode"] not in (None, 0) and not metadata.get("identity_from_path")
compared = 4 if usable else 2
if actual[:compared] != expected[:compared]:
raise RuntimeError("Linked source changed during reconciliation")
if config.MAX_UPLOAD_BYTES and before.st_size > config.MAX_UPLOAD_BYTES:
raise RuntimeError("Linked source exceeds the RAG file size limit")
with os.fdopen(fd, "rb", closefd = False) as src, open(target, "xb") as dst:
_copy_exact(src, dst, before.st_size)
after = os.fstat(fd)
# Both sides are fstat here, so the identity is always comparable.
if (after.st_size, after.st_mtime_ns, after.st_dev, after.st_ino) != actual:
raise RuntimeError("Linked source changed while it was copied")
return str(target)
except Exception:
if target is not None:
_remove_snapshot(str(target))
raise
finally:
os.close(fd)
def _copy_exact(source, target, size: int) -> None:
remaining = size
while remaining:
block = source.read(min(1 << 20, remaining))
if not block:
raise RuntimeError("Linked source changed while it was copied")
target.write(block)
remaining -= len(block)
if source.read(1):View on GitHub (pinned to 203007d190)
Solutions
- Quiesce writers on the linked folder and re-run sync — the next pass snapshots the settled file.
- Sync on a schedule when applications are idle; exclude actively-written directories from the link.
- For log-like data, snapshot from rotated (closed) files rather than the live one.
Defensive patterns
Strategy: retry
Validate before calling
import os
def file_still_settled(path: str, size: int, mtime_ns: int) -> bool:
st = os.stat(path)
return st.st_size == size and st.st_mtime_ns == mtime_ns Try / catch
try:
reconcile(folder_id)
except RuntimeError as e:
if "changed while it was copied" in str(e):
wait_and_resync(folder_id) # snapshot of the settled file lands next cycle
else:
raise Prevention
- Quiesce writers before triggering sync on linked folders.
- Sync rotated/closed files instead of live ones for log-like data.
- On slow network shares, copy hot files locally first and link that copy.
When it happens
Trigger: The source file is written, truncated, or atomically replaced (new inode) during the copy loop itself; large files make the copy window long enough for concurrent writers (logs, exporters, editors) to interleave.
Common situations: Syncing directories that another process actively writes (database export dirs, download folders, live log trees); slow network shares stretching copy time into write windows.
Related errors
- Linked source changed during reconciliation
- Linked folder changed after it was selected
- Linked folder root identity changed during scan
- File escaped the linked folder
- Linked source is not a regular file
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/2a5046f7d03c6d61.
Report an issue: GitHub.