Skip to content

[Bug]: Disabled app sharing a namespace with an enabled app crashes occ upgrade (uncatchable PHP fatal in SchemaChecker::applyDisabledMigrations) #64859

Description

@sacrefizz

⚠️ This issue respects the following points: ⚠️

  • This is not a troubleshooting question, general support matter, or webserver/proxy problem, but likely a bug.
  • 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.
  • 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

Since #63701, SchemaChecker::getFindings() replays the migrations of disabled apps. applyDisabledMigrations() loads their migration files with require_once:

https://github.com/nextcloud/server/blob/6b9285768/lib/private/DB/SchemaChecker.php#L141-L150

If a disabled app declares a migration class that an enabled app has already declared (same app namespace), PHP stops with Cannot redeclare class. That is a compile-time fatal error, not a \Throwable, so the surrounding try { … } catch (\Throwable) never runs and the whole process dies with exit code 255.

Two commands are affected:

  • occ db:schema:check crashes instead of reporting.
  • occ upgrade finishes the upgrade ("Update successful", maintenance mode off), then crashes in postUpgradeCheck() → checkSchema(). Its docblock says the check is "informational only and never fails the upgrade", but here it does. In the official Docker image (and images built on it) the entrypoint runs occ upgrade, so the container exits and restarts. Orchestrators that wait for the container to become healthy (depends_on: service_healthy) give up and leave the dependent containers (web server, cron) unstarted. Result: the instance is down after every update until someone intervenes.

A real-world trigger is common: integration_google and its fork google_synchronization share the Google namespace (unsupported together, see nextcloud/integration_google#142, but people end up with both). Here the fork was disabled and still took every upgrade down. Removing it (occ app:remove --keep-data google_synchronization) makes both commands work again.

Steps to reproduce

  1. Nextcloud 35.0.1 (same code on master).
  2. Install and enable integration_google (4.4.0).
  3. Install google_synchronization (4.1.0) and leave it disabled. Both ship lib/Migration/Version03001001Date20241111105515.php in namespace OCA\Google\Migration.
  4. Run occ db:schema:check → PHP fatal, exit code 255.
  5. Or upgrade (observed on 35.0.0 → 35.0.1 with a Docker image built on the official one): occ upgrade prints "Update successful", then dies with the same fatal.

Any two apps whose migration classes share a fully qualified name should reproduce it the same way.

Expected behavior

A disabled app must never be able to crash occ upgrade or occ db:schema:check. If its migration classes cannot be loaded safely (already declared), skip that app and report it as a non-blocking finding or warning.

One possible fix: before require_once, build the expected class name from the app namespace (appinfo/info.xml) and the file name, as MigrationService does. If class_exists($fqcn, false) is true, skip the app. Loading the files in a separate process would also isolate the fatal.

Nextcloud Server version

35 (35.0.1)

Operating system

Other (TrueNAS SCALE 25.04, Nextcloud app = Docker image built on the official one)

PHP engine version

PHP 8.5 (8.5.11)

Web server

Apache (supported) — from the image, behind an nginx reverse proxy

Database engine version

PostgreSQL (17.11)

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

Updated from a MINOR version (ex. 32.0.1 to 32.0.2)

Are you using the Nextcloud Server Encryption module?

Encryption is Disabled

What user-backends are you using?

  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other

Configuration report

Not included: the fatal happens before any configuration matters, and the reproduction only depends on the two apps above.

List of activated Apps

57 apps enabled; the relevant ones are integration_google 4.4.0 (enabled) and google_synchronization 4.1.0 (disabled).

Nextcloud Signing status

Not relevant (both apps are from the App Store, unmodified).

Nextcloud Logs

Upgrading nextcloud from 35.0.0.10 ...
Turned on maintenance mode
Updating database schema
Updated database
Starting code integrity check...
Finished code integrity check
Update successful
Turned off maintenance mode
Resetting log level
PHP Fatal error:  Cannot redeclare class OCA\Google\Migration\Version03001001Date20241111105515 (previously declared in /var/www/html/custom_apps/integration_google/lib/Migration/Version03001001Date20241111105515.php:21) in /var/www/html/custom_apps/google_synchronization/lib/Migration/Version03001001Date20241111105515.php on line 20
Stack trace:
#0 /var/www/html/lib/private/DB/SchemaChecker.php(146): require_once()
#1 /var/www/html/lib/private/DB/SchemaChecker.php(53): OC\DB\SchemaChecker->applyDisabledMigrations('google_synchron...', Object(Doctrine\DBAL\Schema\Schema), Array)
#2 /var/www/html/core/Command/Upgrade.php(250): OC\DB\SchemaChecker->getFindings()
#3 /var/www/html/core/Command/Upgrade.php(241): OC\Core\Command\Upgrade->checkSchema(Object(Symfony\Component\Console\Output\ConsoleOutput))
#4 /var/www/html/core/Command/Upgrade.php(200): OC\Core\Command\Upgrade->postUpgradeCheck(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
...
#11 /var/www/html/occ(15): require_once('/var/www/html/c...')
#12 {main}

Additional info

occ db:schema:check alone reproduces the fatal (exit code 255) without changing anything. After removing the disabled fork it runs to completion.

Investigation and write-up assisted by Claude Code; the logs, code references and reproduction were checked on the affected instance.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions