Leantime/leantime · error · RuntimeException
Directory "%s" was not created
Error message
Directory "%s" was not created
What it means
After clearing any previous install, installMarketplacePlugin() calls mkdir($pluginDir) without the recursive flag to create app/Plugins/{Folder}. If mkdir() returns false and the directory still does not exist, it throws RuntimeException with the sprintf'ed path - a generic PHP mkdir failure surfaced verbatim. The message tells you exactly which directory could not be created.
Source
Thrown at app/Domain/Plugins/Services/Plugins.php:729
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')),
\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,
};
View on GitHub (pinned to 9a9f49f100)
Solutions
- Ensure app/Plugins exists and is writable by the web server user: chown -R www-data: app/Plugins && chmod u+w app/Plugins.
- Check free space and quota: df -h app/Plugins.
- If the plugin path is nested, create the missing parent directories by hand or patch the call to mkdir($pluginDir, 0755, true).
- Check the SELinux/audit log for denials on the plugins directory and restore proper context (restorecon -R app/Plugins).
- Verify open_basedir includes the plugin directory.
Example fix
// before
if (! mkdir($pluginDir) && ! is_dir($pluginDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $pluginDir));
}
// after: recursive + surface the real OS-level reason
if (! is_dir($pluginDir) && ! @mkdir($pluginDir, 0755, true) && ! is_dir($pluginDir)) {
$reason = error_get_last()['message'] ?? 'unknown error';
throw new \RuntimeException(sprintf('Directory "%s" was not created: %s', $pluginDir, $reason));
} Defensive patterns
Strategy: validation
Validate before calling
if (! is_writable($this->pluginDirectory) || ! is_dir($this->pluginDirectory)) {
throw new RuntimeException('app/Plugins is missing or not writable - plugin cannot be installed');
}
if (disk_free_space($this->pluginDirectory) < $uncompressedBytes) {
throw new RuntimeException('Not enough disk space to extract the plugin');
} Try / catch
try {
$plugins->installMarketplacePlugin($plugin, $version);
} catch (\RuntimeException $e) {
if (str_starts_with($e->getMessage(), 'Directory "')) {
Log::error('Plugin dir create failed: '.$e->getMessage());
return back()->with('error', 'Server could not create the plugin directory - check permissions/disk.');
}
throw $e;
} Prevention
- chown -R <webuser>: app/Plugins and keep it writable before any install.
- Reserve disk headroom for extracted plugin sizes, not just the zips.
- If using a custom plugin directory, create the full parent path up front.
When it happens
Trigger: mkdir() failing because: the parent app/Plugins is not writable by the PHP user; the disk or quota is exhausted; open_basedir forbids the path; SELinux denies directory creation; or pluginDirectory is misconfigured so parents in the path do not exist (non-recursive mkdir cannot create intermediate directories).
Common situations: app/Plugins owned by root after a CLI operation while php-fpm runs as www-data; disk full right after writing the temp zip; a custom LEAN plugin directory pointing at a not-yet-created nested path; hardened SELinux contexts on /var/www.
Related errors
- notification.plugin_cant_remove
- notifications.plugin_install_cant_find_composer
- notification.plugin_cant_download
- notification.plugin_zip_no_file
- notification.plugin_zip_cant_open
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/4c30d16742594a7a.
Report an issue: GitHub.