Leantime/leantime · error · Exception
notification.plugin_cant_download
Error message
notification.plugin_cant_download
What it means
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'.
Source
Thrown at app/Domain/Plugins/Services/Plugins.php:718
throw new RequestException($response);
}
if ($response->header('Content-Type') !== 'application/zip') {
throw new RequestException($response);
}
$filename = $response->header('Content-Disposition');
$filename = substr($filename, strpos($filename, 'filename=') + 9);
$foldername = Str::studly(basename($filename, '.zip'));
$filename = Str::finish($foldername, '.zip');
if (
! file_put_contents(
$temporaryFile = Str::finish(sys_get_temp_dir(), '/').$filename,
$response->body()
)
) {
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')),View on GitHub (pinned to 9a9f49f100)
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.
Defensive patterns
Strategy: retry
Validate before calling
$tmpDir = sys_get_temp_dir();
if (! is_writable($tmpDir)) {
throw new RuntimeException("Temp dir {$tmpDir} is not writable - fix sys_temp_dir/open_basedir before installing plugins");
}
if (disk_free_space($tmpDir) < $expectedZipBytes * 2) {
throw new RuntimeException('Not enough temp space for plugin download + extraction');
} Try / catch
retry: install attempts (1..3) => {
try {
return $plugins->installMarketplacePlugin($plugin, $version);
} catch (\Exception $e) {
if ($e->getMessage() !== __('notification.plugin_cant_download') || $installAttempts === 3) {
throw $e;
}
usleep(500_000 * $installAttempts); // temp-write failures are often transient
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- notification.plugin_zip_no_file
- notifications.plugin_install_cant_find_composer
- notification.plugin_cant_remove
- Directory "%s" was not created
- notification.plugin_zip_exists
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/44c408be3c497e1a.
Report an issue: GitHub.