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

  1. 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.
  2. Free temp space and retry the install (transient truncation is the most common cause).
  3. If the artifact is reproducibly corrupt, download it manually with curl -OJ using the same license headers and report the version to Leantime.
  4. 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

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


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/ba359875110fc8c8. Report an issue: GitHub.