getgrav/grav · error · RuntimeException

Failed to update {file}: {message}

Error message

Failed to update {file}: {message}

What it means

YamlUpdater::save() re-serializes a YAML file it previously parsed into lines/items, tries a line-preserving update, falls back to Yaml::dump, and writes the result. Any exception thrown inside — Symfony Yaml parse/dump errors on malformed content or edge-case indentation, or file write problems — is caught and rethrown as RuntimeException('Failed to update <file>: <inner message>'), where {file} is basename(filename) and {message} is the original exception text. The wrapper identifies which config file broke and preserves the root cause.

Source

Thrown at system/src/Grav/Installer/YamlUpdater.php:77

        try {
            if (!$this->isHandWritten()) {
                $yaml = Yaml::dump($this->items, 5, 2);
            } else {
                $yaml = implode("\n", $this->lines);

                $items = Yaml::parse($yaml);
                if ($items !== $this->items) {
                    // Lines and items are out of sync — fall back to dumping
                    // from items directly.  Loses original formatting but
                    // guarantees the file content matches the intended state.
                    $yaml = Yaml::dump($this->items, 5, 2);
                }
            }

            file_put_contents($this->filename, $yaml);

        } catch (\Exception $e) {
            throw new \RuntimeException('Failed to update ' . basename($this->filename) . ': ' . $e->getMessage());
        }

        return true;
    }

    /**
     * @return bool
     */
    public function isHandWritten(): bool
    {
        return !empty($this->comments);
    }

    /**
     * @return array
     */
    public function getComments(): array
    {

View on GitHub (pinned to 6040efed04)

Solutions

  1. Read the {message} part first — it is the underlying cause: a Yaml parse/dump error points at the offending syntax, a write warning points at permissions.
  2. For syntax issues, validate the file: php -r "echo json_encode(Symfony\Component\Yaml\Yaml::parseFile('user/config/xxx.yaml'));" or re-save it from the admin panel to normalize formatting, then retry the update.
  3. For write issues, make the file and its directory writable by the PHP process (chown/chmod, e.g. chmod 664 file && chmod 775 dir).
  4. Keep a backup of the YAML file before upgrades so you can restore and re-apply changes manually if the updater cannot round-trip hand-written formatting.

Example fix

# before: root-owned config file, updater save fails
$ ls -l user/config/system.yaml
-rw-r--r-- 1 root root 4021 ...   # web user cannot write

# after
$ chown www-data:www-data user/config/system.yaml && chmod 664 user/config/system.yaml
$ bin/grav clearcache
Defensive patterns

Strategy: try-catch

Validate before calling

// Preflight: file parseable and writable before running updates
$updater = \Grav\Installer\YamlUpdater::load($filename) ?? \Grav\Installer\YamlUpdater::loadOrCreate($filename);
if (!\is_writable($filename) || !\is_writable(\dirname($filename))) {
    throw new \RuntimeException("{$filename} is not writable; fix permissions first.");
}
try {
    \Symfony\Component\Yaml\Yaml::parseFile($filename); // catches malformed YAML early
} catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
    throw new \RuntimeException('Fix YAML syntax first: ' . $e->getMessage());
}

Try / catch

try {
    $updater->save();
} catch (\RuntimeException $e) {
    if (str_starts_with($e->getMessage(), 'Failed to update ')) {
        // message embeds basename + root cause; surface it, keep a backup, and retry after fix
        $log->error($e->getMessage());
        $updater->backup(); // copy the original file aside before any manual edit
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Running $updater->save() after executing updates against a YAML file that contains syntax the updater's line/item diff cannot round-trip and Symfony Yaml also fails to parse/dump (mixed tabs, control characters, broken multiline strings); target file or its directory not writable so file_put_contents throws; concurrent modification leaving the parsed state inconsistent.

Common situations: Grav/plugin upgrades rewriting config/system.yaml, user/config files, or .yaml blueprints that were hand-edited with unusual formatting; deployments where config files are root-owned; YAML files containing PHP-style constants or custom tags Symfony Yaml rejects.

Related errors


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