{"record":{"id":"60b7f931572a95d6","repo":"getgrav/grav","slug":"failed-to-update-file-message","errorCode":null,"errorMessage":"Failed to update {file}: {message}","messagePattern":"Failed to update (.+?): (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Installer/YamlUpdater.php","lineNumber":77,"sourceCode":"        try {\n            if (!$this->isHandWritten()) {\n                $yaml = Yaml::dump($this->items, 5, 2);\n            } else {\n                $yaml = implode(\"\\n\", $this->lines);\n\n                $items = Yaml::parse($yaml);\n                if ($items !== $this->items) {\n                    // Lines and items are out of sync — fall back to dumping\n                    // from items directly.  Loses original formatting but\n                    // guarantees the file content matches the intended state.\n                    $yaml = Yaml::dump($this->items, 5, 2);\n                }\n            }\n\n            file_put_contents($this->filename, $yaml);\n\n        } catch (\\Exception $e) {\n            throw new \\RuntimeException('Failed to update ' . basename($this->filename) . ': ' . $e->getMessage());\n        }\n\n        return true;\n    }\n\n    /**\n     * @return bool\n     */\n    public function isHandWritten(): bool\n    {\n        return !empty($this->comments);\n    }\n\n    /**\n     * @return array\n     */\n    public function getComments(): array\n    {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Installer/YamlUpdater.php#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","For write issues, make the file and its directory writable by the PHP process (chown/chmod, e.g. chmod 664 file && chmod 775 dir).","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."],"exampleFix":"# before: root-owned config file, updater save fails\n$ ls -l user/config/system.yaml\n-rw-r--r-- 1 root root 4021 ...   # web user cannot write\n\n# after\n$ chown www-data:www-data user/config/system.yaml && chmod 664 user/config/system.yaml\n$ bin/grav clearcache","handlingStrategy":"try-catch","validationCode":"// Preflight: file parseable and writable before running updates\n$updater = \\Grav\\Installer\\YamlUpdater::load($filename) ?? \\Grav\\Installer\\YamlUpdater::loadOrCreate($filename);\nif (!\\is_writable($filename) || !\\is_writable(\\dirname($filename))) {\n    throw new \\RuntimeException(\"{$filename} is not writable; fix permissions first.\");\n}\ntry {\n    \\Symfony\\Component\\Yaml\\Yaml::parseFile($filename); // catches malformed YAML early\n} catch (\\Symfony\\Component\\Yaml\\Exception\\ParseException $e) {\n    throw new \\RuntimeException('Fix YAML syntax first: ' . $e->getMessage());\n}","typeGuard":null,"tryCatchPattern":"try {\n    $updater->save();\n} catch (\\RuntimeException $e) {\n    if (str_starts_with($e->getMessage(), 'Failed to update ')) {\n        // message embeds basename + root cause; surface it, keep a backup, and retry after fix\n        $log->error($e->getMessage());\n        $updater->backup(); // copy the original file aside before any manual edit\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Keep config/*.yaml owned and writable by the PHP process; check with is_writable() in deploy scripts.","Validate YAML syntax after hand edits (Symfony Yaml::parseFile or a linter) before running upgrades.","Back up user/config before plugin/theme/Grav upgrades so a failed round-trip update can be restored."],"tags":["yaml","updater","installer","grav","file-write","configuration"],"backgroundTag":"file-write-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}