{"record":{"id":"44c408be3c497e1a","repo":"Leantime/leantime","slug":"notification-plugin-cant-download","errorCode":null,"errorMessage":"notification.plugin_cant_download","messagePattern":"notification\\.plugin_cant_download","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"app/Domain/Plugins/Services/Plugins.php","lineNumber":718,"sourceCode":"            throw new RequestException($response);\n        }\n\n        if ($response->header('Content-Type') !== 'application/zip') {\n            throw new RequestException($response);\n        }\n\n        $filename = $response->header('Content-Disposition');\n        $filename = substr($filename, strpos($filename, 'filename=') + 9);\n        $foldername = Str::studly(basename($filename, '.zip'));\n        $filename = Str::finish($foldername, '.zip');\n\n        if (\n            ! file_put_contents(\n                $temporaryFile = Str::finish(sys_get_temp_dir(), '/').$filename,\n                $response->body()\n            )\n        ) {\n            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')),","sourceCodeStart":700,"sourceCodeEnd":736,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Plugins/Services/Plugins.php#L700-L736","documentation":"Inside installMarketplacePlugin(), the zip body was already fetched successfully (HTTP 200, Content-Type application/zip) and the filename was derived from the Content-Disposition header. The error fires when file_put_contents() to sys_get_temp_dir()/{Folder}.zip returns falsy - i.e., the download itself worked but writing the local temp file failed. The translated message is 'Could not download plugin'.","triggerScenarios":"installMarketplacePlugin() where: the temp directory is not writable (open_basedir restriction, read-only /tmp in a hardened container); the disk/tmpfs backing /tmp is full; the parsed filename is empty because Content-Disposition lacked 'filename=' so the target path degenerates to the directory '/tmp/'; a path collision with an existing directory of the same name.","commonSituations":"Docker/Kubernetes images mounting /tmp read-only or with a tiny tmpfs size limit; shared hosting where open_basedir excludes the system temp dir; full disk during large plugin downloads; sys_temp_dir pointed somewhere odd via PHP config.","solutions":["php -r 'var_dump(is_writable(sys_get_temp_dir()));' under the web SAPI - if false, fix permissions or set sys_temp_dir to a writable path.","Check open_basedir in php.ini / .user.ini includes the temp directory.","df -h <tempdir> and free space (the zip plus libzip's temp copy need roughly 2x the archive size).","Mount a writable /tmp (docker-compose: tmpfs: with adequate size, or remove :ro).","If Content-Disposition is malformed (empty filename), report the marketplace header issue and patch the filename parsing."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"$tmpDir = sys_get_temp_dir();\nif (! is_writable($tmpDir)) {\n    throw new RuntimeException(\"Temp dir {$tmpDir} is not writable - fix sys_temp_dir/open_basedir before installing plugins\");\n}\nif (disk_free_space($tmpDir) < $expectedZipBytes * 2) {\n    throw new RuntimeException('Not enough temp space for plugin download + extraction');\n}","typeGuard":null,"tryCatchPattern":"retry: install attempts (1..3) => {\n    try {\n        return $plugins->installMarketplacePlugin($plugin, $version);\n    } catch (\\Exception $e) {\n        if ($e->getMessage() !== __('notification.plugin_cant_download') || $installAttempts === 3) {\n            throw $e;\n        }\n        usleep(500_000 * $installAttempts); // temp-write failures are often transient\n    }\n}","preventionTips":["Keep sys_get_temp_dir() on a writable volume with headroom of at least 2x the largest plugin.","Ensure open_basedir includes the temp directory.","In containers, mount /tmp tmpfs with a sane size or leave it writable.","Monitor disk space before scheduled marketplace installs."],"tags":["plugins","marketplace","filesystem","temp-dir"],"backgroundTag":"temp-directory-not-writable","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}