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

  1. Ensure app/Plugins exists and is writable by the web server user: chown -R www-data: app/Plugins && chmod u+w app/Plugins.
  2. Check free space and quota: df -h app/Plugins.
  3. If the plugin path is nested, create the missing parent directories by hand or patch the call to mkdir($pluginDir, 0755, true).
  4. Check the SELinux/audit log for denials on the plugins directory and restore proper context (restorecon -R app/Plugins).
  5. 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

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


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/4c30d16742594a7a. Report an issue: GitHub.