Leantime/leantime · error · Exception
notification.plugin_zip_read_err
Error message
notification.plugin_zip_read_err
What it means
The match maps ZipArchive::ER_READ to 'Zip: Read error'. libzip hit an I/O read failure while parsing the archive structure out of the temp file - typically because the file is shorter than its internal structures claim (premature EOF during read) or the storage layer failed mid-read. Like ER_INCONS this indicates a damaged/incomplete body, but detected at read time rather than the consistency check.
Source
Thrown at app/Domain/Plugins/Services/Plugins.php:742
) {
throw new \Exception(__('notification.plugin_cant_remove'));
}
if (! mkdir($pluginDir) && ! is_dir($pluginDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $pluginDir));
}
$zip = new \ZipArchive;
match ($zip->open($temporaryFile)) {
\ZipArchive::ER_EXISTS => throw new \Exception(__('notification.plugin_zip_exists')),
\ZipArchive::ER_INCONS => throw new \Exception(__('notification.plugin_zip_inconsistent')),
\ZipArchive::ER_INVAL => throw new \Exception(__('notification.plugin_zip_invalid_arg')),
\ZipArchive::ER_MEMORY => throw new \Exception(__('notification.plugin_zip_malloc')),
\ZipArchive::ER_NOENT => throw new \Exception(__('notification.plugin_zip_no_file')),
\ZipArchive::ER_NOZIP => throw new \Exception(__('notification.plugin_zip_not_zip')),
\ZipArchive::ER_OPEN => throw new \Exception(__('notification.plugin_zip_cant_open')),
\ZipArchive::ER_READ => throw new \Exception(__('notification.plugin_zip_read_err')),
\ZipArchive::ER_SEEK => throw new \Exception(__('notification.plugin_zip_seek_err')),
default => throw new \Exception(__('notification.plugin_zip_unknown_err')),
true => null,
};
if (! $zip->extractTo($pluginDir)) {
throw new \Exception(__('notification.plugin_zip_cant_extract'));
}
$zip->close();
unlink($temporaryFile);
// read the composer.json content from the plugin phar file
$pluginModel = $this->createPluginFromComposer($foldername, $plugin->license);
if (! $this->pluginRepository->addPlugin($pluginModel)) {
throw new \Exception(__('notification_cant_add_to_db'));View on GitHub (pinned to 9a9f49f100)
Solutions
- Compare the temp file size with the response Content-Length header (log both during install) to confirm truncation.
- Retry the install - transient truncation from the marketplace is the most common cause.
- Check tmpfs/disk capacity and health: df -h; dmesg | grep -i error.
- If reproducible, download manually with curl (same license headers) and verify against the marketplace artifact size.
Defensive patterns
Strategy: retry
Validate before calling
$declared = (int) $response->header('Content-Length');
$actual = strlen($response->body());
if ($declared > 0 && $actual !== $declared) {
throw new RuntimeException("Short download ({$actual}/{$declared} bytes) - retry");
} Try / catch
catch (\Exception $e) {
if ($e->getMessage() === __('notification.plugin_zip_read_err')) {
// I/O read failure on a short body: refetch once before giving up
return retryInstall($plugin, $version, attempts: 2);
}
throw $e;
} Prevention
- Compare body length with Content-Length immediately after download.
- Keep temp storage healthy and roomy; watch dmesg for disk errors.
- Treat read errors on freshly downloaded files as transient first, systematic second.
When it happens
Trigger: installMarketplacePlugin() where the temp zip is shorter than the response claimed: a truncated chunked transfer that still ended 'successfully', a tmpfs that silently dropped data when full, or real disk I/O errors on the underlying volume.
Common situations: Marketplace/CDN serving short bodies intermittently; /tmp tmpfs hitting its size cap during the write; failing disks on self-hosted instances.
Related errors
- notification.plugin_zip_exists
- notification.plugin_zip_inconsistent
- notification.plugin_zip_invalid_arg
- notification.plugin_zip_malloc
- notification.plugin_zip_no_file
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/537a6fd598aab352.
Report an issue: GitHub.