How to use GitHub
- Please use the 👍 reaction to show that you are affected by the same issue.
- Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
- Subscribe to receive notifications on status change and new comments.
Steps to reproduce
- User A has a populated top-level personal folder named
collision.
- An administrator creates a Team folder also named
collision and grants User A access to it.
- Trigger mount initialization for User A, for example by opening Files or through concurrent WebDAV/OCS requests. In the observed incident, an OCS Recommendations request resolving a file shared by User A triggered it.
Using a large personal folder and parallel requests makes the race more likely. The affected folder contained about 1.2 GB and 2,300 file-cache entries.
Expected behaviour
- Creating a Team folder must never delete personal data.
- Creating a Team folder should never modify or delete an existing personal folder.
- Ideally, personal folders should not be renamed; doing so during mount initialization is a dangerous design decision.
- At minimum, a name collision must be resolved safely (or ask the user to resolve it explicitly)
Actual behaviour
- All files in the personal folder have been permanently deleted.
- The UI does not reflect the deletion, so it goes unnoticed until a file is accessed.
- The personal folder is renamed to
collision (1) in the file cache, but the physical collision (1) directory is empty.
- Cached files initially remain visible but fail when opened.
occ files:scan later removes the stale entries from the UI.
- The deletion bypasses the trash bin and activity log.
files_versions/collision is not renamed and remains disconnected from the renamed cache path.
Two provider snapshots confirm the result:
- Before:
data/<user>/files/collision contains approximately 1.2 GB.
- After: the original directory is absent,
data/<user>/files/collision (1) is empty, while the database still contains the original file IDs below files/collision (1) and reports approximately 1.2 GB.
Server configuration
Operating system: Unknown; Hetzner-managed Storage Share
Web server: Unknown; Hetzner-managed Storage Share
Database: MySQL/MariaDB
PHP version: Unknown; Hetzner-managed Storage Share
Nextcloud version: 33.0.8.2
Team folders version: 21.0.15
Updated from an older Nextcloud/ownCloud or fresh install: Existing installation; upgrade history unknown
Where did you install Nextcloud from: Hetzner Storage Share managed service
Are you using external storage, if yes which one: No. Local data directory; underlying provider storage is believed to be ZFS-backed. The Team folder uses separate-storage under data/__groupfolders/<id>/.
Are you using encryption: No
Are you using an external user-backend, if yes which one: No
Client configuration
Browser: Chrome 152
Operating system: macOS
Logs
Web server error log
Web server error log
Not available on the managed hosting service.
Nextcloud log (data/nextcloud.log)
Nextcloud log
Names, addresses, and unrelated paths are redacted. All timestamps below are UTC and belong to the same request.
2026-09-08T20:33:50+00:00
reqId: O5VLaScsmckHWBWh6mg7
GET /ocs/v2.php/apps/recommendations/api/v1/recommendations
failed to rename
/var/www/html/data/<user>/files/collision
to
/var/www/html/data/<user>/files/collision (1),
trying copy+delete fallback instead
2026-09-08T20:34:31+00:00
reqId: O5VLaScsmckHWBWh6mg7
opendir(/var/www/html/data/<user>/files/collision):
Failed to open directory: No such file or directory
at /var/www/html/lib/private/Files/Storage/Local.php#130
TypeError: readdir(): Argument #1 ($dir_handle) must be of type resource or null, false given
#0 lib/private/Files/Storage/Common.php(202): readdir(false)
#1 lib/private/Files/Storage/Local.php(401):
Common->copy('files/collision', 'files/collision (1)')
#2 lib/private/Files/Storage/Local.php(381):
Local->copy('files/collision', 'files/collision (1)')
#3 lib/private/Files/Storage/Wrapper/Wrapper.php(136):
Local->rename('files/collision', 'files/collision (1)')
#4 custom_apps/groupfolders/lib/Mount/MountProvider.php(85):
Wrapper->rename('files/collision', 'files/collision (1)')
#5 GroupFolders\Mount\MountProvider::getMountsForUser()
#6 lib/private/Files/SetupManager.php(485): updateNonAuthoritativeProviders(...)
The empty destination directory has mtime 2026-09-08 20:34:31 UTC, exactly matching the opendir/readdir failure.
Browser log
Browser log
Not available.
Analysis
Team folders resolves the collision with three unlocked operations:
$userStorage->rename("files/$originalFolderName", "files/$folderName");
$userCache->move("files/$originalFolderName", "files/$folderName");
$userStorage->getPropagator()->propagateChange("files/$folderName", time());
The return value of the physical rename is ignored, and the cache move is unconditional.
The evidence indicates two concurrent mount-initialization requests. One request renamed the source and moved the cache. The logged request then entered Local::rename()'s copy fallback, which calls remove($target) before reopening the source. It removed the destination containing the other request's successful rename, then found the source missing and recreated an empty destination before throwing on readdir(false).
Atomic filesystem rename does not prevent this: the destructive operation is the later recursive remove($target) performed by the competing request.
Relevant source:
How to use GitHub
Steps to reproduce
collision.collisionand grants User A access to it.Using a large personal folder and parallel requests makes the race more likely. The affected folder contained about 1.2 GB and 2,300 file-cache entries.
Expected behaviour
Actual behaviour
collision (1)in the file cache, but the physicalcollision (1)directory is empty.occ files:scanlater removes the stale entries from the UI.files_versions/collisionis not renamed and remains disconnected from the renamed cache path.Two provider snapshots confirm the result:
data/<user>/files/collisioncontains approximately 1.2 GB.data/<user>/files/collision (1)is empty, while the database still contains the original file IDs belowfiles/collision (1)and reports approximately 1.2 GB.Server configuration
Operating system: Unknown; Hetzner-managed Storage Share
Web server: Unknown; Hetzner-managed Storage Share
Database: MySQL/MariaDB
PHP version: Unknown; Hetzner-managed Storage Share
Nextcloud version: 33.0.8.2
Team folders version: 21.0.15
Updated from an older Nextcloud/ownCloud or fresh install: Existing installation; upgrade history unknown
Where did you install Nextcloud from: Hetzner Storage Share managed service
Are you using external storage, if yes which one: No. Local data directory; underlying provider storage is believed to be ZFS-backed. The Team folder uses
separate-storageunderdata/__groupfolders/<id>/.Are you using encryption: No
Are you using an external user-backend, if yes which one: No
Client configuration
Browser: Chrome 152
Operating system: macOS
Logs
Web server error log
Web server error log
Not available on the managed hosting service.
Nextcloud log (
data/nextcloud.log)Nextcloud log
Names, addresses, and unrelated paths are redacted. All timestamps below are UTC and belong to the same request.
The empty destination directory has mtime
2026-09-08 20:34:31 UTC, exactly matching theopendir/readdirfailure.Browser log
Browser log
Not available.
Analysis
Team folders resolves the collision with three unlocked operations:
The return value of the physical rename is ignored, and the cache move is unconditional.
The evidence indicates two concurrent mount-initialization requests. One request renamed the source and moved the cache. The logged request then entered
Local::rename()'s copy fallback, which callsremove($target)before reopening the source. It removed the destination containing the other request's successful rename, then found the source missing and recreated an empty destination before throwing onreaddir(false).Atomic filesystem rename does not prevent this: the destructive operation is the later recursive
remove($target)performed by the competing request.Relevant source: