flarum/framework · critical · UnreadableManifestException

Cannot read the installed package manifest at $path…

Error message

Cannot read the installed package manifest at $path: $reason. Flarum cannot determine which extensions are installed until this is resolved, which usually means completing or re-running `composer install`. (the file is not valid JSON, so it may be corrupt or only partially written)

What it means

After loading composer's installed.json, ExtensionManager checks json_decode result; a non-array means the manifest is not valid JSON, so UnreadableManifestException::unparsable() is thrown — the file exists but is corrupt, empty, or only partially written.

Solutions

  1. Re-run `composer install` to regenerate a valid installed.json
  2. Delete the corrupt vendor/composer/installed.json and reinstall
  3. Ensure only one composer process runs at a time during deploys
  4. Validate the file: `php -r 'var_dump(is_array(json_decode(file_get_contents("vendor/composer/installed.json"), true)));'`

Example fix

// before
# installed.json truncated: '{"packages": ['
// after
composer install   # rewrites a valid installed.json
Defensive patterns

Strategy: try-catch

Validate before calling

$data = json_decode(file_get_contents($vendor.'/composer/installed.json'), true);
if (!is_array($data)) { /* regenerate before boot */ }

Type guard

function validManifest(string $vendor): bool {
  $raw = @file_get_contents($vendor.'/composer/installed.json');
  return is_array(json_decode($raw ?? '', true));
}

Try / catch

try {
    $extensions = $manager->getExtensions();
} catch (UnreadableManifestException $e) {
    logger()->critical('installed.json corrupt: rerun composer install');
    abort(500);
}

Prevention

When it happens

Trigger: getExtensions() reads {vendor}/composer/installed.json and json_decode returns null/scalar instead of an array (truncated write, concurrent composer run, disk-full, manual edits).

Common situations: Deploys interrupted mid-composer, two composer processes racing on the same install, corrupted artifacts, or files damaged by bad rsync/checkout.

Related errors


AI-assisted analysis of flarum/framework@4b939f6853 (2026-09-15). Data as JSON: /api/errors/089843902971e1ba. Report an issue: GitHub.

Appendix: source

Thrown at framework/core/src/Extension/ExtensionManager.php:69

     * @return Collection<string, Extension>
     */
    public function getExtensions(): Collection
    {
        if (is_null($this->extensions)) {
            $manifest = $this->paths->vendor.'/composer/installed.json';

            if (! $this->filesystem->exists($manifest)) {
                throw UnreadableManifestException::missing($manifest);
            }

            /** @var Collection<string, Extension> $extensions */
            $extensions = new Collection();

            // Load all packages installed by composer.
            $installed = json_decode($this->filesystem->get($manifest), true);

            if (! is_array($installed)) {
                throw UnreadableManifestException::unparsable($manifest);
            }

            // Composer 2.0 changes the structure of the installed.json manifest
            $installed = $installed['packages'] ?? $installed;

            if (! is_array($installed)) {
                throw UnreadableManifestException::unparsable($manifest);
            }

            // We calculate and store a set of composer package names for all installed Flarum extensions,
            // so we know what is and isn't a flarum extension in `calculateDependencies`.
            // Using keys of an associative array allows us to do these checks in constant time.
            $installedSet = [];

            $composerJsonConfs = [];

            foreach ($installed as $package) {
                $name = Arr::get($package, 'name');

View on GitHub (pinned to 4b939f6853)