{"record":{"id":"ba359875110fc8c8","repo":"Leantime/leantime","slug":"notification-plugin-zip-inconsistent","errorCode":null,"errorMessage":"notification.plugin_zip_inconsistent","messagePattern":"notification\\.plugin_zip_inconsistent","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"app/Domain/Plugins/Services/Plugins.php","lineNumber":736,"sourceCode":"            throw new \\Exception(__('notification.plugin_cant_download'));\n        }\n\n        if (\n            is_dir($pluginDir = \"{$this->pluginDirectory}{$foldername}\")\n            && ! File::deleteDirectory($pluginDir)\n        ) {\n            throw new \\Exception(__('notification.plugin_cant_remove'));\n        }\n\n        if (! mkdir($pluginDir) && ! is_dir($pluginDir)) {\n            throw new \\RuntimeException(sprintf('Directory \"%s\" was not created', $pluginDir));\n        }\n\n        $zip = new \\ZipArchive;\n\n        match ($zip->open($temporaryFile)) {\n            \\ZipArchive::ER_EXISTS => throw new \\Exception(__('notification.plugin_zip_exists')),\n            \\ZipArchive::ER_INCONS => throw new \\Exception(__('notification.plugin_zip_inconsistent')),\n            \\ZipArchive::ER_INVAL => throw new \\Exception(__('notification.plugin_zip_invalid_arg')),\n            \\ZipArchive::ER_MEMORY => throw new \\Exception(__('notification.plugin_zip_malloc')),\n            \\ZipArchive::ER_NOENT => throw new \\Exception(__('notification.plugin_zip_no_file')),\n            \\ZipArchive::ER_NOZIP => throw new \\Exception(__('notification.plugin_zip_not_zip')),\n            \\ZipArchive::ER_OPEN => throw new \\Exception(__('notification.plugin_zip_cant_open')),\n            \\ZipArchive::ER_READ => throw new \\Exception(__('notification.plugin_zip_read_err')),\n            \\ZipArchive::ER_SEEK => throw new \\Exception(__('notification.plugin_zip_seek_err')),\n            default => throw new \\Exception(__('notification.plugin_zip_unknown_err')),\n            true => null,\n        };\n\n        if (! $zip->extractTo($pluginDir)) {\n            throw new \\Exception(__('notification.plugin_zip_cant_extract'));\n        }\n\n        $zip->close();\n\n        unlink($temporaryFile);","sourceCodeStart":718,"sourceCodeEnd":754,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Plugins/Services/Plugins.php#L718-L754","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// after download, verify integrity before handing the file to ZipArchive\n$bytes = strlen($response->body());\n$declared = (int) $response->header('Content-Length');\nif ($declared > 0 && $bytes !== $declared) {\n    throw new RuntimeException(\"Truncated download: {$bytes} of {$declared} bytes\");\n}\nif (strncmp($response->body(), \"PK\\x03\\x04\", 4) !== 0) {\n    throw new RuntimeException('Downloaded body is not a zip archive');\n}","typeGuard":null,"tryCatchPattern":"for ($attempt = 1; $attempt <= 3; $attempt++) {\n    try {\n        $plugins->installMarketplacePlugin($plugin, $version);\n        break;\n    } catch (\\Exception $e) {\n        $zipErrors = [__('notification.plugin_zip_inconsistent'), __('notification.plugin_zip_read_err')];\n        if (! in_array($e->getMessage(), $zipErrors, true) || $attempt === 3) {\n            throw $e;\n        }\n        sleep($attempt); // truncated/corrupt transfer - refetch\n    }\n}","preventionTips":["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."],"tags":["plugins","ziparchive","corruption","marketplace"],"backgroundTag":"zip-archive-corrupt","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}