Leantime/leantime · error · Exception
notification.plugin_zip_unknown_err
Error message
notification.plugin_zip_unknown_err
What it means
The default arm of the match over ZipArchive::open(): any return code not explicitly mapped becomes 'Zip: Unknown error'. The unmapped libzip codes include the most instructive ones: ER_TMPOPEN (libzip could not create its own temp copy - /tmp full or unwritable), ER_EOF (premature end of file), ER_CRC (checksum mismatch), ER_ZLIB (zlib unavailable/mismatch), ER_COMPNOTSUPP/ER_ENCRNOTSUPP (unsupported compression or an encrypted zip), ER_INTERNAL. Because the code discards the actual code, the real cause is invisible without instrumentation.
Source
Thrown at app/Domain/Plugins/Services/Plugins.php:744
}
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
- Capture the actual code first: log the return value of $zip->open($temporaryFile) (see exampleFix) - it maps 1:1 to a ZipArchive::ER_* constant.
- Free temp space: libzip duplicates the archive, so /tmp needs about 2x the zip size.
- Verify the zip extension is complete: php -m | grep -e zip -e zlib; reinstall php-zip/php-zlib packages if not.
- Test the same file with unzip -t on the server to distinguish artifact corruption from environment limits.
- If the artifact is encrypted or uses unsupported compression, report it - Leantime's installer cannot extract it.
Example fix
// before
match ($zip->open($temporaryFile)) {
// ... mapped constants ...
default => throw new \Exception(__('notification.plugin_zip_unknown_err')),
true => null,
};
// after: keep the code in the message so the unmapped ER_* is diagnosable
$code = $zip->open($temporaryFile);
if ($code !== true) {
throw new \Exception(__('notification.plugin_zip_unknown_err')." (ZipArchive::open code {$code})");
} Defensive patterns
Strategy: try-catch
Validate before calling
// capture the code and map the constants the installer forgot
$code = $zip->open($temporaryFile);
if ($code !== true) {
$known = [ZipArchive::ER_TMPOPEN => 'temp create failed (tmp full?)', ZipArchive::ER_EOF => 'premature EOF', ZipArchive::ER_CRC => 'CRC mismatch', ZipArchive::ER_ZLIB => 'zlib unavailable', ZipArchive::ER_ENCRNOTSUPP => 'encrypted zip unsupported'];
throw new RuntimeException($known[$code] ?? "ZipArchive::open code {$code}");
} Try / catch
try {
$plugins->installMarketplacePlugin($plugin, $version);
} catch (\Exception $e) {
Log::error('Plugin zip open failed: '.$e->getMessage().' | tmp free: '.disk_free_space(sys_get_temp_dir()));
return back()->with('error', 'Plugin archive could not be opened - check /tmp space and the zip extension.');
} Prevention
- Always log the numeric open() code - 'unknown' errors are undiagnosable without it.
- Keep /tmp space at ~2x the archive size (libzip duplicates the file).
- Verify php-zip is built with zlib support.
- Reject encrypted zips early - the installer cannot extract them.
When it happens
Trigger: installMarketplacePlugin() where open() returns ER_TMPOPEN (needs roughly one extra temp copy of the archive - /tmp exhausted), ER_ENCRNOTSUPP (password-protected zip), ER_ZLIB (PHP zip extension without zlib), ER_EOF/ER_CRC (truncated or damaged body), or any newer libzip code the match does not know.
Common situations: Tight /tmp quota - the downloaded zip plus libzip's internal copy exceed it; marketplace shipping an encrypted or unusually compressed artifact; PHP builds with a minimal zip extension; libzip version differences introducing unmapped codes.
Related errors
- notification.plugin_zip_exists
- notification.plugin_zip_inconsistent
- notification.plugin_zip_malloc
- notification.plugin_zip_not_zip
- notification.plugin_cant_download
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/78826162fd9f255c.
Report an issue: GitHub.