getgrav/grav · error · InstallException
Could not update system configuration to maintain backwards
Error message
Could not update system configuration to maintain backwards compatibility
What it means
This is the postflight hook of the Grav 1.7.0 schema update. It uses YamlUpdater to write backward-compatibility defaults into user/config/system.yaml (twig.autoescape: false, three strict_mode.* flags) and saves; any Exception from that write is rethrown as InstallException 'Could not update system configuration to maintain backwards compatibility'. The core files have already been installed at this point — only the user config mutation failed.
Source
Thrown at system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php:21
use Grav\Installer\InstallException;
use Grav\Installer\VersionUpdate;
use Grav\Installer\YamlUpdater;
return [
'preflight' => null,
'postflight' =>
function () {
/** @var VersionUpdate $this */
try {
// Keep old defaults for backwards compatibility.
$yaml = YamlUpdater::instance(GRAV_ROOT . '/user/config/system.yaml');
$yaml->define('twig.autoescape', false);
$yaml->define('strict_mode.yaml_compat', true);
$yaml->define('strict_mode.twig2_compat', true);
$yaml->define('strict_mode.blueprint_compat', true);
$yaml->save();
} catch (\Exception $e) {
throw new InstallException('Could not update system configuration to maintain backwards compatibility', $e);
}
}
];
View on GitHub (pinned to 6040efed04)
Solutions
- Fix ownership/permissions so the user running direct-install can write user/config/system.yaml (e.g. `chown -R www-data:user user/config`)
- Validate the file first: `bin/grav yamllint user/config/system.yaml` or re-parse it with a YAML linter, and restore from backup if broken
- Free disk space / release locks, then re-run direct-install — the updater is idempotent
Example fix
# before sudo php bin/gpm direct-install tmp/grav-update.zip # root writes, file owned by www-data mode 644 -> write fails # after sudo -u www-data php bin/gpm direct-install tmp/grav-update.zip # run as the owning user
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: config must be writable and parseable before upgrading
$file = GRAV_ROOT . '/user/config/system.yaml';
if (file_exists($file)) {
if (!is_writable($file)) {
throw new \RuntimeException("{$file} is not writable by " . get_current_user());
}
try {
\Symfony\Component\Yaml\Yaml::parseFile($file);
} catch (\Throwable $e) {
throw new \RuntimeException("{$file} is not valid YAML: " . $e->getMessage());
}
} Try / catch
try {
Install::instance()->run();
} catch (InstallException $e) {
$prev = $e->getPrevious(); // the original YamlUpdater/IO failure
fwrite(STDERR, $e->getMessage() . ': ' . ($prev ? $prev->getMessage() : 'unknown'));
// Fix permissions/ownership, then re-run — the update hooks are idempotent
} Prevention
- Run direct-install as the user that owns user/config (e.g. sudo -u www-data)
- Lint user/config/system.yaml before every upgrade
- Keep disk headroom and avoid concurrent admin saves during upgrades
- Read the chained previous exception — it carries the real I/O or parse error
When it happens
Trigger: user/config/system.yaml (or the user/config directory) being read-only or owned by another user when direct-install runs as CLI user; the file containing YAML the updater cannot parse/merge; disk full; file locked by a concurrent edit.
Common situations: CLI upgrade run as root while the file is owned by www-data (or vice versa); restored backups with wrong ownership; system.yaml hand-edited into invalid YAML before upgrading.
Related errors
- Could not update system configuration for Twig compatibility
- Could not remove the retired security.*.xss_scan_output sett
- Could not migrate the Twig-sandbox allowlists to the additiv
- Failed to update {file}: {message}
- Following requirements have failed:
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/1ea635cdea73d7ea.
Report an issue: GitHub.