getgrav/grav · error · RuntimeException

ZipArchiver: ZIP failed to extract {archive_file} to {destin

Error message

ZipArchiver: ZIP failed to extract {archive_file} to {destination}

What it means

When the uncompressed-size cap is disabled (maxSize = 0), ZipArchiver::extract() falls back to the native ZipArchive::extractTo($destination); if that call returns false, the archive is closed and RuntimeException 'ZipArchiver: ZIP failed to extract <file> to <destination>' is thrown. This is a raw extraction failure — permission, space, or archive-integrity level — with no further detail because PHP's ZipArchive does not expose one here.

Source

Thrown at system/src/Grav/Common/Filesystem/ZipArchiver.php:104

                        $totalSize += (int) $stat['size'];
                        if ($totalSize > $maxSize) {
                            $zip->close();
                            throw new RuntimeException('ZipArchiver: refused to extract ' . $this->archive_file . '. Archive exceeds the maximum uncompressed size (' . $maxSize . ' bytes).');
                        }
                    }
                }
            }

            Folder::create($destination);

            if ($maxSize > 0) {
                // Enforce the uncompressed-size cap against bytes actually written,
                // so a forged-small declared size cannot smuggle a bomb past the
                // advisory pre-pass above (GHSA-8h9x-89f2-m7x3).
                $this->extractStreamed($zip, $destination, $numFiles, $maxSize);
            } elseif (!$zip->extractTo($destination)) {
                $zip->close();
                throw new RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
            }

            $zip->close();

            return $this;
        }

        throw new RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
    }

    /**
     * Resolve the uncompressed-size / file-count / nesting-depth caps applied
     * before extraction. Shares the same config keys and defaults as
     * GPM\Installer so both ZIP extraction paths enforce identical limits. A
     * limit of 0 disables that particular check.
     *
     * @return array{0:int,1:int,2:int} [maxUncompressedBytes, maxFiles, maxDepth]
     */

View on GitHub (pinned to 6040efed04)

Solutions

  1. Verify writability of the destination and free space: is_writable($dest), df -h
  2. Test the archive independently: unzip -t package.zip to detect corruption; re-download if it fails
  3. Re-run the extraction once the environment issue (space/permissions/lock) is fixed
  4. If you need size-cap enforcement too, keep max_uncompressed_size > 0 so the streamed path with better errors is used
Defensive patterns

Strategy: try-catch

Validate before calling

if (!is_dir($destination) || !is_writable($destination)) {
            throw new RuntimeException("Destination {$destination} missing or not writable");
}
if (disk_free_space($destination) < $expectedSize) {
    throw new RuntimeException('Insufficient disk space for extraction');
}

Try / catch

try {
    (new ZipArchiver($path))->extract($destination);
} catch (RuntimeException $e) {
    if (str_contains($e->getMessage(), 'failed to extract')) {
        // ZipArchive::extractTo returned false: check perms/space, verify with unzip -t, then retry once
    }
}

Prevention

When it happens

Trigger: Destination directory not writable by the PHP process; disk full during extraction; archive corrupted mid-file so extractTo aborts; extracting into a path blocked by open_basedir; concurrent extraction into the same destination clobbering entries.

Common situations: Plugin/theme install into user/ when the web user lacks write permission; low-disk VPS deployments; truncated downloads of package zips; two simultaneous installs targeting the same folder.

Related errors


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