getgrav/grav · error · RuntimeException

400

400

Error message

Grav has already been installed here!

What it means

Install::prepare() compares the directory of the update package's installer with the directory of the live index.php: if they are the same, copying would overwrite the package into itself, so it aborts with RuntimeException 400 'Grav has already been installed here!'. It means the updater found its own files sitting in the target site root — typically because an update zip was extracted into the live Grav root.

Source

Thrown at system/src/Grav/Installer/Install.php:313

    /**
     * @return void
     * @throws RuntimeException
     */
    public function prepare(): void
    {
        // Locate the new Grav update and the target site from the filesystem.
        $this->ensureLocation();
        $location = realpath(__DIR__);
        $target = realpath(GRAV_ROOT . '/index.php');

        if (!$location) {
            throw new RuntimeException('Internal Error', 500);
        }

        if ($target && dirname($location, 4) === dirname($target)) {
            // We cannot copy files into themselves, abort!
            throw new RuntimeException('Grav has already been installed here!', 400);
        }

        // Load the installer classes.
        foreach ($this->classMap as $class_name => $path) {
            // Make sure that none of the Grav\Installer classes have been loaded, otherwise installation may fail!
            if (class_exists($class_name, false)) {
                throw new RuntimeException(sprintf('Cannot update Grav, class %s has already been loaded!', $class_name), 500);
            }

            require $path;
        }

        $this->legacySupport();

        $this->location = dirname($location, 4);

        $versions = Versions::instance(USER_DIR . 'config/versions.yaml');
        $this->updater = new VersionUpdater('core/grav', __DIR__ . '/updates', $this->getVersion(), $versions);

View on GitHub (pinned to 6040efed04)

Solutions

  1. Keep the downloaded update zip outside the Grav root (e.g. tmp/) and install from it: `bin/gpm direct-install tmp/grav-update.zip`
  2. If the package was already extracted into the webroot, remove that extracted copy (system/, vendor/, etc. that came from the zip) or restore from backup, then run direct-install from the zip

Example fix

# before
cd /var/www/grav && unzip ~/grav-update-v1.8.0.zip -d . && bin/gpm direct-install . # self-copy -> 400

# after
bin/gpm direct-install /tmp/grav-update-v1.8.0.zip # install from the zip outside GRAV_ROOT
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the update source and the live site are not the same directory
$updateDir = realpath($updateZipOrDir);
$targetDir = realpath(GRAV_ROOT . '/index.php') ? dirname(realpath(GRAV_ROOT . '/index.php')) : null;
if ($targetDir && $updateDir && str_starts_with($updateDir, $targetDir . DIRECTORY_SEPARATOR)) {
    throw new \RuntimeException('Update package lives inside the site root — move it to tmp/ first');
}

Prevention

When it happens

Trigger: Unzipping a grav-update package directly into GRAV_ROOT and then running direct-install on/inside it; pointing direct-install at a folder that is the live site itself; running the installer from a copy of Grav that shares the target root.

Common situations: Manually extracting the update to 'just look at it' in the webroot, then upgrading; confusing the download location with the install source in shell history.

Related errors


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