{"record":{"id":"544b15ab4d8c9547","repo":"unslothai/unsloth","slug":"linked-folder-root-identity-changed-during-scan","errorCode":null,"errorMessage":"Linked folder root identity changed during scan","messagePattern":"Linked folder root identity changed during scan","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"studio/backend/core/rag/folder_sync.py","lineNumber":1011,"sourceCode":"                        # rather than abort the scan, which is never authoritative for deletion.\n                        pass\n                rel = os.path.relpath(full, root).replace(os.sep, \"/\")\n                found[rel] = {\n                    \"path\": full,\n                    \"size_bytes\": st.st_size,\n                    \"mtime_ns\": st.st_mtime_ns,\n                    \"device\": st.st_dev,\n                    \"inode\": st.st_ino,\n                    # A recovered identity is comparable to the next scan's, but not to os.fstat's:\n                    # shared-folder and WebDAV drivers report different ids for the two call paths.\n                    \"identity_from_path\": from_path,\n                }\n                if config.FOLDER_MAX_FILES and len(found) > config.FOLDER_MAX_FILES:\n                    raise RuntimeError(\n                        f\"Folder contains more than the {config.FOLDER_MAX_FILES} supported files limit\"\n                    )\n    if _root_identity(root) != identity:\n        raise RuntimeError(\"Linked folder root identity changed during scan\")\n    return found, identity\n\n\ndef _snapshot(root: str, metadata: dict) -> str:\n    source = metadata[\"path\"]\n    resolved = os.path.realpath(source)\n    if not _is_within(root, resolved):\n        raise RuntimeError(\"File escaped the linked folder\")\n    # os.fdopen already forces this descriptor binary on Windows; O_BINARY only guards a raw os.read.\n    flags = os.O_RDONLY | getattr(os, \"O_NOFOLLOW\", 0) | getattr(os, \"O_BINARY\", 0)\n    fd = os.open(source, flags)\n    target = None\n    try:\n        ext = os.path.splitext(source)[1].lower()\n        target = ensure_dir(rag_uploads_root()) / f\"linked-{uuid.uuid4().hex}{ext}\"\n        before = os.fstat(fd)\n        if not stat.S_ISREG(before.st_mode):\n            raise RuntimeError(\"Linked source is not a regular file\")","sourceCodeStart":993,"sourceCodeEnd":1029,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/rag/folder_sync.py#L993-L1029","documentation":"After the directory walk completes, _scan re-runs _root_identity(root) and compares to the identity captured before the walk. If the root directory was replaced/remounted while the scan was running, results would describe a mix of two trees, so the scan result is discarded and the error raised.","triggerScenarios":"Long scans of big folders where, mid-walk, the root is deleted and recreated, renamed-and-replaced, or its mount is swapped (device id change); deployment scripts doing atomic directory swaps during sync.","commonSituations":"Atomic deploy replacing the linked folder; container storage re-mounted during a job; the same race as error 570 but caught after the walk instead of before it.","solutions":["Re-run the sync once the folder is stable; the job will re-scan with the new identity if the row was updated, or fail with the identity-changed error if not.","If the swap was intentional, re-register (or re-authorize) the folder so its stored identity matches the new root.","Schedule folder replacement outside sync windows to avoid racing the worker."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    result = run_scan(folder_id)\nexcept RuntimeError as e:\n    if \"changed during scan\" in str(e):\n        if folder_row_is_unchanged(folder_id):\n            result = run_scan(folder_id)  # transient swap during walk; retry once\n        else:\n            mark_folder_needs_relink(folder_id)\n    else:\n        raise","preventionTips":["Keep the linked root stable while syncs run; do atomic swaps between jobs.","Re-register the folder after intentional replacements so stored identity matches.","Schedule syncs when deployment activity is quiet."],"tags":["rag","folder-sync","race-condition","toctou","scan"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}