getgrav/grav · critical · RuntimeException

Uploaded archive file is not a valid Grav update package

Error message

Uploaded archive file is not a valid Grav update package

What it means

SelfupgradeCommand applies a Grav upgrade the same way as direct-install: the freshly downloaded zip must contain system/install.php returning a callable installer. If the archive GPM fetched does not satisfy that contract (file missing, not callable), the command throws this RuntimeException; any Throwable raised by the bundled installer itself is caught and surfaced through Installer::setError with its message preserved.

Source

Thrown at system/src/Grav/Console/Gpm/SelfupgradeCommand.php:648

            $script = $folder . '/system/install.php';
            if ((file_exists($script) && $install = include $script) && is_callable($install)) {
                if (is_object($install) && method_exists($install, 'setProgressCallback')) {
                    $install->setProgressCallback(function (string $stage, string $message, ?int $percent = null, array $extra = []) {
                        $this->handleServiceProgress($stage, $message, $percent);
                    });
                }
                if (is_object($install) && method_exists($install, 'generatePreflightReport')) {
                    $report = $install->generatePreflightReport();
                    if (!$this->handlePreflightReport($report)) {
                        Installer::setError('Upgrade aborted due to preflight requirements.');

                        return;
                    }
                }
                $install($zip);
            } else {
                throw new RuntimeException('Uploaded archive file is not a valid Grav update package');
            }
        } catch (\Throwable $e) {
            // Catch Throwable (not just Exception) so a PHP Error/TypeError raised
            // by the bundled installer on a newer PHP runtime still produces a
            // meaningful message instead of an uncaught fatal with no context.
            $this->lastThrowable = $e;

            $message = $e->getMessage();
            if ($message === '') {
                $message = 'Unexpected ' . get_class($e) . ' during upgrade';
            }
            Installer::setError($message);

            $this->logUpgrade('error', 'Exception while staging upgrade: ' . $message, [
                'exception' => get_class($e),
                'where' => $e->getFile() . ':' . $e->getLine(),
                'trace' => $e->getTraceAsString(),
            ]);

View on GitHub (pinned to 6040efed04)

Solutions

  1. Upgrade to an official stable release: `bin/gpm self-upgrade -f` (or without -f to the latest stable) and confirm the channel in system config is stable.
  2. If you use a custom/remote GPM endpoint, fix the artifacts it serves: each Grav package must contain system/install.php returning a callable, or point back to getgrav.org.
  3. Verify the downloaded package integrity: check the zip in the GPM temp/cache location with `unzip -t` and confirm system/install.php exists inside.
  4. If the reported message comes from the installer throwing, address that root cause (e.g. raise/lower PHP version, fix folder permissions) before retrying the upgrade.

Example fix

# before
$ bin/gpm self-upgrade   # package from custom proxy without install.php -> error

# after (system.yaml -> gpm: official_endpoint: https://getgrav.org/downloads) 
$ bin/gpm self-upgrade -f
Defensive patterns

Strategy: validation

Validate before calling

// before self-upgrade, confirm endpoint is official and reachable
$endpoint = $grav['config']->get('system.gpm.official_endpoint');
if ($endpoint !== 'https://getgrav.org/downloads') {
    exit("Refusing self-upgrade from unofficial endpoint: {$endpoint}" . PHP_EOL);
}
// then: passthru('php bin/gpm self-upgrade -f');

Prevention

When it happens

Trigger: Running `bin/gpm self-upgrade` to an unstable/prerelease channel or a custom GPM proxy whose package lacks system/install.php; a corrupted/incomplete download served with a 200 status; GPM response pointing at a source-only artifact; upgrading to a version whose installer throws (PHP incompatibility, TypeError) — that path reports the installer's own message instead.

Common situations: Self-hosted/private GPM server returning repacked zips without the install script; proxy or CDN mangling the package (truncation, HTML error page saved as zip); testing upgrades to RC builds from GitHub; PHP runtime newer than the bundled installer supports, turning the installer call into an Error.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/b6ffb7d7b9c82f0b. Report an issue: GitHub.