Steps to reproduce
- User A adds an SMB external storage mount.
- User A shares a folder from that mount directly with User B.
- Something changes on the SMB server outside of Nextcloud.
- User A browses the folder → gets rescanned correctly.
- User B browses the re-share → does not get rescanned (stale listing).
Root cause
SharedStorage::getWatcher() decides between a real Watcher and a
NullWatcher purely from string metadata on the share's cache entry:
https://github.com/nextcloud/server/blob/master/apps/files_sharing/lib/SharedStorage.php#L453-L477
$node = $this->getShare()->getNodeCacheEntry();
if ($node instanceof CacheEntry) {
$storageId = $node->getData()['storage_string_id'] ?? null;
if ($storageId !== null && !(str_starts_with($storageId, 'home::') || str_starts_with($storageId, 'object::user'))) {
// real watcher, rescans happen
}
}
// falls through here -> NullWatcher, rescans never happen
$this->watcher = new NullWatcher();
getNodeCacheEntry() returns null, or a CacheEntry missing the
storage_string_id key, for several legitimate code paths:
DefaultShareProvider::getSharesBy() / getShareById() / getSharesByPath()
never populate a node cache entry at all (no filecache/storages join).
- Even
DefaultShareProvider::_getSharedWith() (used for the normal
getMountsForUser/getMountsForPath mount setup) can come back without
storage_string_id if the source node wasn't in filecache yet at
share-fetch time.
Whenever that happens, the code silently assumes "home storage" and installs
a NullWatcher, meaning the share never gets checked against the real
backend for the lifetime of that storage object/request.
This is the same fragile spot as #50235, which was patched defensively in
#50769 (?? null to avoid the crash) — that fixed the exception but left the
underlying "silently never rescan" behavior in place.
Suggested fix
Fall back to a live, deterministic check of the real underlying storage
($this->nonMaskedStorage->instanceOfStorage(IHomeStorage::class), after
$this->init()) when the cache-entry metadata is missing or incomplete,
instead of assuming home storage. This mirrors the existing fallback pattern
in SharedStorage::getSourceRootInfo(). A FailedStorage guard is needed so
a deleted owner / offline storage still safely yields NullWatcher.
I'm happy to submit a PR for this if the approach sounds right.
This issue has been created in cooperation with Claude Sonnet 5 but it has been reviewed and submitted by me - human.
Steps to reproduce
Root cause
SharedStorage::getWatcher()decides between a realWatcherand aNullWatcherpurely from string metadata on the share's cache entry:https://github.com/nextcloud/server/blob/master/apps/files_sharing/lib/SharedStorage.php#L453-L477
getNodeCacheEntry()returnsnull, or aCacheEntrymissing thestorage_string_idkey, for several legitimate code paths:DefaultShareProvider::getSharesBy()/getShareById()/getSharesByPath()never populate a node cache entry at all (no
filecache/storagesjoin).DefaultShareProvider::_getSharedWith()(used for the normalgetMountsForUser/getMountsForPathmount setup) can come back withoutstorage_string_idif the source node wasn't infilecacheyet atshare-fetch time.
Whenever that happens, the code silently assumes "home storage" and installs
a
NullWatcher, meaning the share never gets checked against the realbackend for the lifetime of that storage object/request.
This is the same fragile spot as #50235, which was patched defensively in
#50769 (
?? nullto avoid the crash) — that fixed the exception but left theunderlying "silently never rescan" behavior in place.
Suggested fix
Fall back to a live, deterministic check of the real underlying storage
(
$this->nonMaskedStorage->instanceOfStorage(IHomeStorage::class), after$this->init()) when the cache-entry metadata is missing or incomplete,instead of assuming home storage. This mirrors the existing fallback pattern
in
SharedStorage::getSourceRootInfo(). AFailedStorageguard is needed soa deleted owner / offline storage still safely yields
NullWatcher.I'm happy to submit a PR for this if the approach sounds right.
This issue has been created in cooperation with Claude Sonnet 5 but it has been reviewed and submitted by me - human.