Leantime/leantime · error · Exception
notification.plugin_zip_inconsistent
Error message
notification.plugin_zip_inconsistent
What it means
The match maps ZipArchive::ER_INCONS to 'Zip: Archive inconsistent'. libzip returns ER_INCONS when the archive's structural consistency check fails - the end-of-central-directory record or central directory does not agree with the local headers, i.e. the file is truncated or internally corrupt. The marketplace response already passed the 'application/zip' content-type check, so the body itself is damaged.
Source
Thrown at app/Domain/Plugins/Services/Plugins.php:736
throw new \Exception(__('notification.plugin_cant_download'));
}
if (
is_dir($pluginDir = "{$this->pluginDirectory}{$foldername}")
&& ! File::deleteDirectory($pluginDir)
) {
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);View on GitHub (pinned to 9a9f49f100)
Solutions
- Inspect the temp file: head -c 4 /tmp/{Folder}.zip must be 'PK' magic, and unzip -t /tmp/{Folder}.zip should report no errors - compare its size with the response Content-Length.
- Free temp space and retry the install (transient truncation is the most common cause).
- If the artifact is reproducibly corrupt, download it manually with curl -OJ using the same license headers and report the version to Leantime.
- Patch the install to compare strlen($response->body()) against the Content-Length header before writing.
Defensive patterns
Strategy: retry
Validate before calling
// after download, verify integrity before handing the file to ZipArchive
$bytes = strlen($response->body());
$declared = (int) $response->header('Content-Length');
if ($declared > 0 && $bytes !== $declared) {
throw new RuntimeException("Truncated download: {$bytes} of {$declared} bytes");
}
if (strncmp($response->body(), "PK\x03\x04", 4) !== 0) {
throw new RuntimeException('Downloaded body is not a zip archive');
} Try / catch
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
$plugins->installMarketplacePlugin($plugin, $version);
break;
} catch (\Exception $e) {
$zipErrors = [__('notification.plugin_zip_inconsistent'), __('notification.plugin_zip_read_err')];
if (! in_array($e->getMessage(), $zipErrors, true) || $attempt === 3) {
throw $e;
}
sleep($attempt); // truncated/corrupt transfer - refetch
}
} Prevention
- Verify downloaded size against Content-Length before extracting.
- Keep temp space comfortably larger than the largest plugin artifact.
- Retry once on corrupt-archive errors before escalating - marketplace truncation is usually transient.
When it happens
Trigger: installMarketplacePlugin() where the downloaded temp zip is truncated: server closed the connection early behind a '200' (proxy/CDN truncation), the body was cut mid-transfer, /tmp ran out of space mid-write, or the artifact on marketplace.leantime.io is genuinely corrupt.
Common situations: Flaky network between the host and the marketplace; an undersized tmpfs filling up during the write; a transparent proxy re-writing responses; a bad artifact published for that plugin/version.
Related errors
- notification.plugin_zip_exists
- notification.plugin_zip_malloc
- notification.plugin_zip_not_zip
- notification.plugin_zip_unknown_err
- notification.plugin_cant_download
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/ba359875110fc8c8.
Report an issue: GitHub.