{"record":{"id":"4c30d16742594a7a","repo":"Leantime/leantime","slug":"directory-s-was-not-created","errorCode":null,"errorMessage":"Directory \"%s\" was not created","messagePattern":"Directory \"(.+?)\" was not created","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"app/Domain/Plugins/Services/Plugins.php","lineNumber":729,"sourceCode":"\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')),\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","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Plugins/Services/Plugins.php#L711-L747","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nif (! mkdir($pluginDir) && ! is_dir($pluginDir)) {\n    throw new \\RuntimeException(sprintf('Directory \"%s\" was not created', $pluginDir));\n}\n\n// after: recursive + surface the real OS-level reason\nif (! is_dir($pluginDir) && ! @mkdir($pluginDir, 0755, true) && ! is_dir($pluginDir)) {\n    $reason = error_get_last()['message'] ?? 'unknown error';\n    throw new \\RuntimeException(sprintf('Directory \"%s\" was not created: %s', $pluginDir, $reason));\n}","handlingStrategy":"validation","validationCode":"if (! is_writable($this->pluginDirectory) || ! is_dir($this->pluginDirectory)) {\n    throw new RuntimeException('app/Plugins is missing or not writable - plugin cannot be installed');\n}\nif (disk_free_space($this->pluginDirectory) < $uncompressedBytes) {\n    throw new RuntimeException('Not enough disk space to extract the plugin');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $plugins->installMarketplacePlugin($plugin, $version);\n} catch (\\RuntimeException $e) {\n    if (str_starts_with($e->getMessage(), 'Directory \"')) {\n        Log::error('Plugin dir create failed: '.$e->getMessage());\n        return back()->with('error', 'Server could not create the plugin directory - check permissions/disk.');\n    }\n    throw $e;\n}","preventionTips":["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."],"tags":["plugins","filesystem","mkdir","permissions"],"backgroundTag":"mkdir-permission-denied","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}