[Bug]: Deleting a folder on external storage (SFTP) returns 403 even though the delete succeeds — Storage::doDelete() re-attempts an already-completed delete
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 36.9k
- Forks
- 5.2k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 713
Description
Bug description
When deleting a folder located on an external storage mount (SFTP backend)
from the desktop client or web UI, the server returns HTTP 403 Forbidden,
and the desktop client shows a sync error notification — even though the
folder was actually deleted successfully on the remote storage.
Root cause
files_trashbin tries to move the folder to trash first
(Trashbin::move2trash(), apps/files_trashbin/lib/Trashbin.php). On this
SFTP backend the trash "move" isn't atomic, so it falls into the fallback
path (lines ~368-405): it deletes the source directly via
$sourceStorage->rmdir($sourceInternalPath), which succeeds. However this
fallback path still sets $moveSuccessful = false and the function returns
false regardless of whether the direct delete succeeded.
Back in Storage::doDelete() (apps/files_trashbin/lib/Storage.php, line
~142-148), a false return from moveToTrash() causes a second
rmdir/unlink call on the original wrapped storage for the same path —
which no longer exists, since it was already removed in step 1. This second
call fails on the SFTP backend and that failure is what surfaces to the
client as 403, despite the delete having already succeeded.
Confirmed server-side (access log):
DELETE .../StorageBox/Personal_old/del%20mac HTTP/1.1" 403 655
and confirmed the folder was in fact gone by connecting directly via sftp
to the storage backend immediately after.
nextcloud.log shows the trigger:
app: files_trashbin, message: "trash move failed, removing trash metadata and payload"
Steps to reproduce
- Add an SFTP external storage (files_external, backend
publickey::rsa_privateor similar — anything where moveFromStorage
can't do an atomic rename to a.d<timestamp>trash location). - Create a folder with at least one file inside it, on that external
storage. - Delete the folder (web UI or desktop client).
Expected behavior
Delete succeeds and returns 204/200, consistent with the fact that the
folder is actually removed. No trashbin entry is expected since trash
isn't supported here — that's fine — but the client should not receive an
error for an operation that in fact completed.
Actual behavior
Server returns 403 Forbidden. Desktop client shows a persistent sync error
notification and blacklists the item for up to 1h
(OCC::PropagateRemoteDelete, "Probably firewall error: 403"), even though
nothing is actually wrong with permissions — the delete completed.
Deleting individual files inside the same folder does not exhibit this
(returns 204) — only deleting the directory itself triggers the double-
delete path.
Related issues
Similar high-level symptom (folder deleted but client shown an error, not
in trash) reported for Local storage cross-device rename failures in #14743
and its duplicate #38569 — different root cause (PHP rename()
cross-device), but possibly the same underlying Storage::doDelete()
"return false → retry rmdir on an already-deleted path" pattern. Worth
checking if fixing this generically in Storage::doDelete() (skip the
retry when the source no longer exists) would also close those.
Installation method
Community Docker image (docker.io/library/nextcloud:34-apache)
Nextcloud Server version
34.0.1.2
Operating system
Debian (container), host: Raspberry Pi OS (aarch64)
PHP engine version
(as shipped in nextcloud:34-apache)
Web server
Apache (bundled)
Additional context
External storage backend: SFTP with public key auth
(publickey::rsa_private), remote is a Hetzner Storage Box. Desktop client
version 4.0.6-1 (Debian built), Qt 6.10.2.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/files_trashbin/lib/Trashbin.php around the fallback in Trashbin::move2trash(), then trace the false result through apps/files_trashbin/lib/Storage.php around lines 142-148 and Storage::doDelete(). Reproduce the SFTP directory deletion case and verify that a successful direct delete does not trigger a second rmdir/unlink or return an error; also consider the related local-storage cases in issues #14743 and #38569.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100