Bug description
The Notes app’s hook registration in NotesHooks.php calls listenTo() on a LazyFolder during the app’s boot() phase. This forces materialization of the LazyFolder via LazyFolder::__call() → getRealFolder(), which then triggers:
Files\Node\Root::getValueArray()
AppConfig::getTypedValue()
AppConfig::matchAndApplyLexiconDefinition()
AppConfig::getLexiconPreset()
As a result, getLexiconPreset() is invoked on every Nextcloud request (index.php, ocs/v2.php, remote.php), causing severe performance degradation — especially when the lazy AppConfig backend is slow or under load.
This appears to be the same anti-pattern that was fixed for the files_external app in PR #55830 (fix(files-external): do not load lazy appconfig from construct), but Notes still uses the old approach.
Steps to reproduce
- Install Nextcloud 33.0.2 with Notes app 4.13.1 enabled.
- Enable PHP-FPM slowlog with
request_slowlog_timeout = 10s.
- Generate normal traffic (DAV uploads, OCS calls, web UI usage).
- Observe slowlog accumulating thousands of entries with the stack trace below.
- Run
occ app:disable notes — slowlog accumulation stops immediately.
Expected behavior
The Notes app should register its hooks/listeners using the modern IRegistrationContext::registerEventListener() pattern in the register() method, not by calling listenTo() on a LazyFolder during boot().
LazyFolder should only materialize on-demand when an event actually fires, not on every request during boot.
Actual behavior
Every Nextcloud request triggers full materialization of LazyFolder and lazy AppConfig loading through the Notes app boot process, leading to:
- Heavy CPU load on every request
- ~20,700 slowlog entries in 24 hours on a moderately used instance
- Visible 30+ second hangs for end users during file uploads and folder browsing
- Effective service unavailability under sustained traffic
In our case, this caused approximately 12 hours of degraded service before we identified and disabled the Notes app.
Stack trace
Reproduced 1161 times in a single 24-hour log file. Sample entry:
[24-Apr-2026 04:02:28] [pool www] pid 912049
script_filename = /var/www/nextcloud/ocs/v2.php
[0x...] getLexiconPreset() /var/www/nextcloud/lib/private/AppConfig.php:1726
[0x...] matchAndApplyLexiconDefinition() /var/www/nextcloud/lib/private/AppConfig.php:481
[0x...] getTypedValue() /var/www/nextcloud/lib/private/AppConfig.php:453
[0x...] getValueArray() /var/www/nextcloud/lib/private/Files/Node/Root.php:83
[0x...] __construct() /var/www/nextcloud/lib/private/Server.php:401
[0x...] {closure:OC\Server::__construct():396}() /var/www/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:186
[0x...] offsetGet() /var/www/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:149
[0x...] query() /var/www/nextcloud/lib/private/ServerContainer.php:155
[0x...] query() /var/www/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:46
[0x...] get() /var/www/nextcloud/lib/private/Server.php:433
[0x...] {closure:{closure:OC\Server::__construct():431}:432}() /var/www/nextcloud/lib/private/Files/Node/LazyFolder.php:51
[0x...] call_user_func() /var/www/nextcloud/lib/private/Files/Node/LazyFolder.php:51
[0x...] getRealFolder() /var/www/nextcloud/lib/private/Files/Node/LazyFolder.php:65
[0x...] __call() /var/www/nextcloud/lib/private/Files/Node/LazyFolder.php:79
[0x...] listen() /var/www/nextcloud/apps/notes/lib/AppInfo/NotesHooks.php:75
[0x...] listenTo() /var/www/nextcloud/apps/notes/lib/AppInfo/NotesHooks.php:35
[0x...] register() /var/www/nextcloud/apps/notes/lib/AppInfo/Application.php:47
[0x...] {closure:OCA\Notes\AppInfo\Application::boot():46}() /var/www/nextcloud/lib/private/AppFramework/Bootstrap/FunctionInjector.php:28
[0x...] injectFn() /var/www/nextcloud/lib/private/AppFramework/Bootstrap/BootContext.php:32
Suggested fix
Refactor NotesHooks.php and Application.php to use IRegistrationContext::registerEventListener() for the relevant filesystem events (NodeCreatedEvent, NodeDeletedEvent, etc.) instead of calling listenTo() on a LazyFolder during boot.
This should ensure that:
- listeners are registered without instantiating the
Root folder
LazyFolder remains lazy until actually needed
- lazy
AppConfig is not loaded on every request
Environment
- Notes app version: 4.13.1
- Nextcloud version: 33.0.2
- OS: Debian 13
- Web server: Apache 2.4
- PHP version: 8.4
- Database: MariaDB
- Storage: NFS-backed ZFS RAIDZ2
Related
Bug description
The Notes app’s hook registration in
NotesHooks.phpcallslistenTo()on aLazyFolderduring the app’sboot()phase. This forces materialization of theLazyFolderviaLazyFolder::__call()→getRealFolder(), which then triggers:Files\Node\Root::getValueArray()AppConfig::getTypedValue()AppConfig::matchAndApplyLexiconDefinition()AppConfig::getLexiconPreset()As a result,
getLexiconPreset()is invoked on every Nextcloud request (index.php,ocs/v2.php,remote.php), causing severe performance degradation — especially when the lazyAppConfigbackend is slow or under load.This appears to be the same anti-pattern that was fixed for the
files_externalapp in PR #55830 (fix(files-external): do not load lazy appconfig from construct), but Notes still uses the old approach.Steps to reproduce
request_slowlog_timeout = 10s.occ app:disable notes— slowlog accumulation stops immediately.Expected behavior
The Notes app should register its hooks/listeners using the modern
IRegistrationContext::registerEventListener()pattern in theregister()method, not by callinglistenTo()on aLazyFolderduringboot().LazyFoldershould only materialize on-demand when an event actually fires, not on every request during boot.Actual behavior
Every Nextcloud request triggers full materialization of
LazyFolderand lazyAppConfigloading through the Notes app boot process, leading to:In our case, this caused approximately 12 hours of degraded service before we identified and disabled the Notes app.
Stack trace
Reproduced 1161 times in a single 24-hour log file. Sample entry:
Suggested fix
Refactor
NotesHooks.phpandApplication.phpto useIRegistrationContext::registerEventListener()for the relevant filesystem events (NodeCreatedEvent,NodeDeletedEvent, etc.) instead of callinglistenTo()on aLazyFolderduring boot.This should ensure that:
RootfolderLazyFolderremains lazy until actually neededAppConfigis not loaded on every requestEnvironment
Related
fix(files-external): do not load lazy appconfig from construct— same anti-pattern, fixed forfiles_externalAppConfigRuntimeExceptionmechanismAppConfig