nextcloud / nextcloud/server

[Bug]: UserConfig throws UnknownKeyException after concurrent preference insert when both caches are loaded

Open
#64,191 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage 34-feedback bug
Dominant language
PHP
Stars
36.9k
Forks
5.2k
Avg merge
2d 3h
Merged PRs (30d)
713

Description

⚠️ This issue respects the following points: ⚠️
  • This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug (if unsure, ask the Community Help Forum).
  • This issue is not already reported on Github OR solved at the Community Help Forum (I've searched!).
  • I'm using a maintained major version of Nextcloud Server and tested against the latest patch level. (Supported major versions and current patch levels).
  • I agree to follow Nextcloud's Code of Conduct.
  • I've tried my best to provide clear reproduction steps that someone unfamiliar with this bug could use to reproduce it.
Bug description

I have seen this error intermittently for several users on Nextcloud 34.0.3.2. One example occurred during a background PUT /ocs/v2.php/apps/user_status/api/v1/heartbeat?format=json request. The stack goes through token-login completion and Activity's SetUserDefaults listener before failing in core UserConfig::setTypedValue():

unknown key activity configured for <redacted-user> even though insert raised a duplicate contraint

Activity is trying to save activity/configured=yes. Core handles the duplicate insert by calling loadConfigAll(), but that does not read the database again if both cache halves are already marked loaded. The newly inserted preference is still missing from valueDetails, so core throws UnknownKeyException. Activity does not catch the exception from its final configured write.

The heartbeat handler is not reached. The inspected OCS v2 error path returns HTTP 500; the supplied exception log does not itself record the HTTP response status. The exact overlapping production request has not been identified.

Steps to reproduce

This can be reproduced deterministically without timing two HTTP requests. In an isolated test fixture:

  1. Create two independent OC\Config\UserConfig instances, A and B, sharing the same preferences store. Start with no preferences for a synthetic user.
  2. Call B->getApps($uid) so both the fast and lazy caches are loaded while activity/configured is absent.
  3. Call A->setValueMixed($uid, "activity", "configured", "yes"); the insert succeeds.
  4. Call the same setter on B. It encounters a uniqueness conflict and throws the exception above instead of discovering the row.

Illustrative sequence (A and B must be separate instances, not two references to the same container service):

$uid = "synthetic-user";
$b->getApps($uid);
$a->setValueMixed($uid, "activity", "configured", "yes");
$b->setValueMixed($uid, "activity", "configured", "yes"); // throws

The standalone check executed the actual installed UserConfig and Activity listener with an in-memory database double modeling unique preference keys and minimal service doubles. It also exercised the final write through SetUserDefaults::handle(new PostLoginEvent(...)).

Results:

  • The exact exception escapes the Activity listener.
  • Failed duplicate recovery performs zero additional database reads.
  • One correct configured=yes row remains stored.
  • A fresh UserConfig instance sees the row and Activity skips initialization.
  • Control: if only the fast cache was loaded before A inserted, B performs the full reread and recovers.

The same assertions passed with the exact 34.0.4 UserConfig.php from commit 77c7284fed1f5c7b4bce1a94299e229f3281b606. This is method-level testing with a modeled database, not an end-to-end 34.0.4 installation, a real SQL concurrency test, or a claim that the production interleaving was captured.

Expected behavior

When another request has already inserted the same preference, core should reread the existing row and complete the normal update/no-op handling, preserving the stored type and flags. Saving an already-persisted activity/configured=yes marker should not abort login completion or an otherwise valid API request.

Nextcloud Server version

34

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.5

Web server

Apache (supported)

Database engine version

MariaDB

Is this bug present after an update or on a fresh install?

None

Are you using the Nextcloud Server Encryption module?

None

What user-backends are you using?
  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other
Configuration report
Relevant context only; full production config:list output is omitted.
Accounts: LDAP/Active Directory.
Browser authentication: automatic Windows/Kerberos SSO through Apache.
user_saml type: environment-variable
user_saml general-uid_mapping: REMOTE_USER
The isolated reproduction does not use LDAP, Kerberos, or user_saml.
List of activated Apps
Relevant apps, not a full app:list dump:
- activity (bundled app; installed test copy reports 7.0.0)
- user_status (the affected heartbeat endpoint)
- user_ldap
- user_saml 8.3.1
The error is thrown by core UserConfig during Activity preference initialization.
Nextcloud Signing status

Nextcloud Logs
Redacted, shortened exception excerpt; unrelated fields and repetitive dispatcher frames omitted:

Nextcloud: 34.0.3.2
Time: 2026-09-09T06:26:56+00:00
Level: 3
Method: PUT
URL: /ocs/v2.php/apps/user_status/api/v1/heartbeat?format=json
User: <redacted-user>
Exception: OCP\Config\Exceptions\UnknownKeyException
Message: unknown key activity configured for <redacted-user> even though insert raised a duplicate contraint
Thrown at: lib/private/Config/UserConfig.php:1227

Call chain (request entry to failing write):
ocs/v2.php -> ocs/v1.php:72
-> OC::handleLogin()
-> OC\User\Session::tryTokenLogin()
-> loginWithToken()
-> completeLogin()
-> PostLoginEvent
-> OCA\Activity\Listener\SetUserDefaults::handle():37
-> SetUserDefaults::setDefaultsForUser():69
-> OC\AllConfig::setUserValue():226
-> OC\Config\UserConfig::setValueMixed():920
-> UserConfig::setTypedValue():1227
Additional info

Versions: production Nextcloud 34.0.3 (internal 34.0.3.2), Apache 2.4.66, PHP-FPM 8.5.4, MariaDB 11.8; affected browser reported Edge 152 on Windows. The standalone check ran on PHP 8.5.4. Exact installation/upgrade history and server-side encryption status were not collected.

Latest patch check: 34.0.4 was released on September 10. Its UserConfig.php is byte-for-byte identical to the 34.0.3 file tested, and the reproduction was repeated against that exact 34.0.4 source.

Source references:

Related changes: #62586 / #63093 added the explicit exception and diagnostic reason, but did not force a cache refresh. Activity #2791 catches exceptions around individual notification-default writes; the final configured marker remains outside that catch.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with lib/private/Config/UserConfig.php around the duplicate-insert recovery at line 1209 and the cache loader around line 1815. Then inspect Activity's lib/Listener/SetUserDefaults.php around line 30 and reproduce the separate-instance sequence described in the issue. Done means the existing preference is discovered after the conflict, its type and flags are preserved, and the final marker write no longer throws.

Written by the indexing model from the issue text.

Assessment

Tech stack
mariadb, php
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.