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
- 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.
- 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.
- Verify the downloaded package integrity: check the zip in the GPM temp/cache location with `unzip -t` and confirm system/install.php exists inside.
- 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
- Keep the GPM official endpoint at getgrav.org; scrutinize any private GPM proxy's artifacts.
- Test upgrades on a staging copy first when using custom packages.
- After a failed self-upgrade, inspect Installer::getError() output (shown by the command) — it carries the installer's real message when the bundled script threw.
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
- Uploaded archive file is not a valid Grav update package
- Following requirements have failed:
- 400
- Could not migrate the Twig-sandbox allowlists to the additiv
- No backups defined...
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/b6ffb7d7b9c82f0b.
Report an issue: GitHub.