unslothai/unsloth · error · ValueError

Linked folder changed after it was selected

Error message

Linked folder changed after it was selected

What it means

Raised inside create_folder's transaction when re-computed _root_identity(normalized) differs from the identity captured at function entry (root_device, root_inode), or from the caller-supplied expected_identity. This is a TOCTOU guard: between the user 'selecting' a folder and the row being inserted, the directory was replaced (new inode) or remounted (new device).

Source

Thrown at studio/backend/core/rag/folder_sync.py:268

                "SELECT * FROM linked_folders WHERE scope=?", (scope,)
            ).fetchall()
            for row in existing:
                existing_key = _path_key(row["path"])
                if existing_key == normalized_key or _same_file(row["path"], normalized):
                    if row["status"] == "retired" or row["delete_remove_index"] is not None:
                        raise ValueError("Linked folder is still being removed")
                    conn.rollback()
                    return _reauthorize_folder(row["id"], normalized, expected_identity)
                if _paths_overlap(existing_key, normalized_key):
                    raise ValueError("Linked folders in the same scope cannot overlap")
            try:
                current_identity = _root_identity(normalized)
            except RuntimeError as exc:
                raise ValueError(str(exc)) from exc
            if current_identity != (root_device, root_inode) or (
                expected_identity is not None and current_identity != expected_identity
            ):
                raise ValueError("Linked folder changed after it was selected")
            conn.execute(
                "INSERT INTO linked_folders(id, scope_type, scope_id, scope, path, name, "
                "root_device, root_inode, auto_sync, status, created_at, updated_at) "
                "VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
                (
                    folder_id,
                    scope_type,
                    scope_id,
                    scope,
                    normalized,
                    (name or Path(normalized).name or normalized).strip(),
                    *_store_identity((root_device, root_inode)),
                    int(auto_sync),
                    "pending",
                    now,
                    now,
                ),
            )

View on GitHub (pinned to 203007d190)

Solutions

  1. Retry create_folder without expected_identity (or with a freshly captured identity) after confirming the directory is the intended one.
  2. If the swap was intentional (deployment replaced the folder), capture the new st_dev/st_ino and pass it as expected_identity.
  3. Serialize concurrent creates for the same path with the scope lock / UI-level disabling of the button.

Example fix

# before
expected = stat_dir(path)  # captured long before
create_folder(..., expected_identity=expected)

# after
expected = os.stat(path), then immediately:
create_folder(..., expected_identity=(s.st_dev, s.st_ino))
# on ValueError 'changed after it was selected': re-stat and retry once
Defensive patterns

Strategy: try-catch

Validate before calling

# Capture identity immediately before the call to shrink the window:
st = os.stat(path)
expected = (st.st_dev, st.st_ino)
create_folder(..., expected_identity=expected)

Try / catch

try:
    folder = create_folder(..., expected_identity=expected)
except ValueError as e:
    if "changed after it was selected" in str(e):
        st = os.stat(path)  # re-capture and retry once
        folder = create_folder(..., expected_identity=(st.st_dev, st.st_ino))
    else:
        raise

Prevention

When it happens

Trigger: A swap: rm -rf dir && mv other dir between the pre-check and the INSERT; concurrent create_folder calls where one already re-authorized the row; the folder being on removable media that unmounted/remounted mid-call; expected_identity captured in the UI flow from a stale stat.

Common situations: Installer/updater scripts replacing directories atomically; two tabs linking the same folder simultaneously; USB/network drives with unstable device ids; a frontend sending an expected_identity captured before a file-system migration.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/1bb9af52d74867e3. Report an issue: GitHub.