getgrav/grav · error · RuntimeException
ZipArchiver: Failed to open {archive_file}
Error message
ZipArchiver: Failed to open {archive_file} What it means
ZipArchiver::extract() begins with $zip->open($this->archive_file); any result other than true (corrupt archive, missing file, unreadable permissions, wrong format) skips the whole extraction body and falls through to RuntimeException 'ZipArchiver: Failed to open <file>'. Unlike compress(), this path does not map the ZipArchive error code to a reason, so the caller only learns the open failed. It is the entry-point integrity gate for every extraction.
Source
Thrown at system/src/Grav/Common/Filesystem/ZipArchiver.php:112
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]
*/
protected function archiveLimits(): array
{
$config = Grav::instance()['config'] ?? null;
$maxSize = $config ? (int) $config->get('system.gpm.archive.max_uncompressed_size', 1073741824) : 1073741824; // 1 GiB
$maxFiles = $config ? (int) $config->get('system.gpm.archive.max_files', 50000) : 50000;
$maxDepth = $config ? (int) $config->get('system.gpm.archive.max_depth', 48) : 48;
View on GitHub (pinned to 6040efed04)
Solutions
- Confirm the file exists, is readable (is_file/is_readable), and has a plausible size — a few-KB 'zip' is often an HTML error page
- Validate integrity outside PHP: unzip -t file.zip (exit code 0 = intact); re-download the package from its official source if broken
- Check the ZipArchive open error yourself first (see validationCode) to get the specific ER_* code instead of the generic message
- Ensure the zip PHP extension and libzip are current if large/zip64 archives are involved
Example fix
// before
(new ZipArchiver($zipPath))->extract($destination); // generic 'Failed to open'
// after — surface the real ZipArchive error first
$zip = new ZipArchive();
$res = $zip->open($zipPath);
if ($res !== true) {
throw new RuntimeException(sprintf('Cannot open %s: ZipArchive error code %d', $zipPath, $res));
}
$zip->close();
(new ZipArchiver($zipPath))->extract($destination); Defensive patterns
Strategy: validation
Validate before calling
if (!is_file($zipPath) || !is_readable($zipPath) || filesize($zipPath) < 22) {
throw new RuntimeException("{$zipPath} is not a readable zip file");
}
$probe = new ZipArchive();
$res = $probe->open($zipPath);
if ($res !== true) {
throw new RuntimeException(sprintf('ZipArchive refuses %s: error code %d', $zipPath, $res));
}
$probe->close(); Try / catch
try {
(new ZipArchiver($path))->extract($destination);
} catch (RuntimeException $e) {
if (str_contains($e->getMessage(), 'Failed to open')) {
// corrupt/truncated/unreadable archive — re-download from a trusted source, then retry
}
} Prevention
- Verify downloads (size/checksum) before extraction — truncated zips are the top cause
- Reject tiny 'zip' files; they are usually HTML error pages from a failed fetch
- Open archives once yourself to get the ER_* code when debugging the generic message
When it happens
Trigger: Extracting a file that is not a real zip (HTML error page saved as .zip from a failed download); truncated/interrupted download; file removed or unreadable between setArchive() and extract(); encrypted archives opened without a password; zip extension reading a >4 GiB zip64 file on an old libzip.
Common situations: GPM package downloads interrupted by proxies returning partial content; manually uploaded packages with wrong extension; permissions changed after upload; exotic zip variants produced by nonstandard packers.
Related errors
- ZipArchiver: failed to read entry "{name}" from {archive_fil
- ZipArchiver: refused to extract {archive_file}. Archive exce
- ZipArchiver: refused to extract {archive_file}. Entry "{name
- ZipArchiver: refused to extract {archive_file}. Entry "{name
- ZipArchiver: refused to extract {archive_file}. Archive exce
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/7d41d6748b29466e.
Report an issue: GitHub.