{"record":{"id":"70fb2c78a5ea512f","repo":"getgrav/grav","slug":"versions-file-cannot-be-read","errorCode":null,"errorMessage":"Versions file cannot be read","messagePattern":"Versions file cannot be read","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"system/src/Grav/Installer/Versions.php","lineNumber":322,"sourceCode":"        $var = array_pop($path);\n        $current = &$this->items;\n\n        foreach ($path as $field) {\n            if (!is_array($current) || !isset($current[$field])) {\n                return;\n            }\n            $current = &$current[$field];\n        }\n\n        unset($current[$var]);\n        $this->updated = true;\n    }\n\n    private function __construct(protected string $filename)\n    {\n        $content = is_file($this->filename) ? file_get_contents($this->filename) : null;\n        if (false === $content) {\n            throw new \\RuntimeException('Versions file cannot be read');\n        }\n        $this->items = $content ? Yaml::parse($content) : [];\n    }\n}\n","sourceCodeStart":304,"sourceCodeEnd":327,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Installer/Versions.php#L304-L327","documentation":"Versions (a final class used by Grav's installer/upgrader) lazily reads user/config/versions.yaml in its private constructor via file_get_contents(). The file existence is checked with is_file(), but if reading still returns false — permissions, ACLs, open_basedir restrictions, or a race deleting the file — it throws RuntimeException 'Versions file cannot be read'. Because Versions is memoized per filename, this aborts installer initialization.","triggerScenarios":"Versions::instance('/path/versions.yaml') where the file exists but is unreadable by the PHP process (mode 000/600 owned by another user, open_basedir excluding the path); user/config/versions.yaml made unwritable/unreadable by a deploy or restore; running CLI install/upgrades (bin/grav, scheduler) as a different user than the web server.","commonSituations":"Shared hosting with restrictive file modes; files transferred by root/rsync leaving root-owned versions.yaml; security hardening that chmod 600'd config files; containers where the config volume is mounted read-only or with a different uid.","solutions":["Fix permissions on user/config/versions.yaml so the PHP process can read AND write it (typically chown to the web user and chmod 644 or 664).","If open_basedir is active, ensure the Grav user/config path is inside the allowed paths.","When running bin/grav upgrade/clearcache from CLI, run it as the same user that owns the Grav files (sudo -u www-data ...).","As a last resort, delete versions.yaml (Grav recreates it; you lose installed-version history, forcing plugins/themes to re-declare versions on next upgrade)."],"exampleFix":"# before (shell)\n$ ls -l user/config/versions.yaml\n-rw------- 1 root root 1240 ...   # unreadable by web user -> exception\n\n# after\n$ chown www-data:www-data user/config/versions.yaml && chmod 664 user/config/versions.yaml","handlingStrategy":"validation","validationCode":"$file = USER_DIR . 'config/versions.yaml';\nif (\\is_file($file) && !\\is_readable($file)) {\n    throw new \\RuntimeException('Fix permissions on ' . $file . ' before continuing.');\n}\n$versions = \\Grav\\Installer\\Versions::instance($file);","typeGuard":"function versionsFileIsReadable(string $filename): bool\n{\n    return !\\is_file($filename) || \\is_readable($filename);\n}","tryCatchPattern":"try {\n    $versions = \\Grav\\Installer\\Versions::instance();\n} catch (\\RuntimeException $e) {\n    if ('Versions file cannot be read' === $e->getMessage()) {\n        // report an actionable permission error to the operator\n        fwrite(STDERR, 'Make user/config/versions.yaml readable by this process (chown/chmod), then retry.');\n        exit(1);\n    }\n    throw $e;\n}","preventionTips":["Keep all files under user/config owned and writable by the PHP process user; verify after rsync/root deployments.","Run CLI upgrades (bin/grav upgrade) as the web user, not root.","Add a deploy-time check: is_readable/is_writable on user/config/versions.yaml fails the deploy loudly."],"tags":["installer","versions","file-permissions","grav","configuration"],"backgroundTag":"config-file-unreadable","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}