{"record":{"id":"78826162fd9f255c","repo":"Leantime/leantime","slug":"notification-plugin-zip-unknown-err","errorCode":null,"errorMessage":"notification.plugin_zip_unknown_err","messagePattern":"notification\\.plugin_zip_unknown_err","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"app/Domain/Plugins/Services/Plugins.php","lineNumber":744,"sourceCode":"        }\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);\n\n        // read the composer.json content from the plugin phar file\n        $pluginModel = $this->createPluginFromComposer($foldername, $plugin->license);\n\n        if (! $this->pluginRepository->addPlugin($pluginModel)) {\n            throw new \\Exception(__('notification_cant_add_to_db'));\n        }\n    }","sourceCodeStart":726,"sourceCodeEnd":762,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Plugins/Services/Plugins.php#L726-L762","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nmatch ($zip->open($temporaryFile)) {\n    // ... mapped constants ...\n    default => throw new \\Exception(__('notification.plugin_zip_unknown_err')),\n    true => null,\n};\n\n// after: keep the code in the message so the unmapped ER_* is diagnosable\n$code = $zip->open($temporaryFile);\nif ($code !== true) {\n    throw new \\Exception(__('notification.plugin_zip_unknown_err').\" (ZipArchive::open code {$code})\");\n}","handlingStrategy":"try-catch","validationCode":"// capture the code and map the constants the installer forgot\n$code = $zip->open($temporaryFile);\nif ($code !== true) {\n    $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'];\n    throw new RuntimeException($known[$code] ?? \"ZipArchive::open code {$code}\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    $plugins->installMarketplacePlugin($plugin, $version);\n} catch (\\Exception $e) {\n    Log::error('Plugin zip open failed: '.$e->getMessage().' | tmp free: '.disk_free_space(sys_get_temp_dir()));\n    return back()->with('error', 'Plugin archive could not be opened - check /tmp space and the zip extension.');\n}","preventionTips":["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."],"tags":["plugins","ziparchive","marketplace","diagnostics"],"backgroundTag":"ziparchive-open-error","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}