getgrav/grav · error · RuntimeException

Unable to create directory: %s

Error message

Unable to create directory: %s

What it means

Folder::create() (alias mkdir) wraps PHP mkdir($folder, 0777, true); if the recursive creation fails and a re-check (with clearstatcache) still shows no directory, it throws RuntimeException 'Unable to create directory: /path'. The double-check exists because mkdir can report failure while the directory actually exists (race); the throw therefore means the directory genuinely could not be created — permissions, open_basedir, path collision with an existing file, or disk exhaustion.

Source

Thrown at system/src/Grav/Common/Filesystem/Folder.php:476

    /**
     * @param  string  $folder
     * @return void
     * @throws RuntimeException
     */
    public static function create($folder)
    {
        // Silence error for open_basedir; should fail in mkdir instead.
        if (@is_dir($folder)) {
            return;
        }

        $success = @mkdir($folder, 0777, true);

        if (!$success) {
            // Take yet another look, make sure that the folder doesn't exist.
            clearstatcache(true, $folder);
            if (!@is_dir($folder)) {
                throw new RuntimeException(sprintf('Unable to create directory: %s', $folder));
            }
        }
    }

    /**
     * Recursive copy of one directory to another
     *
     * @param string $src
     * @param string $dest
     * @return bool
     * @throws RuntimeException
     */
    public static function rcopy($src, $dest, $preservePermissions = false)
    {

        // If the src is not a directory do a simple file copy
        if (!is_dir($src)) {
            copy($src, $dest);

View on GitHub (pinned to 6040efed04)

Solutions

  1. Check write permission on the deepest existing ancestor: ls -ld $(dirname $folder) and compare with the PHP process user (whoami / posix_geteuid)
  2. chown/chmod the parent chain so the web user can create children (e.g. chown -R www-data cache/ && chmod 755 cache/)
  3. Verify open_basedir covers every path segment: php -i | grep open_basedir
  4. Confirm nothing occupies the path as a file (ls -la $folder) and that the volume is not read-only/full (df -h, df -i)

Example fix

// before
Folder::create(GRAV_ROOT . '/cache/compiled/pages'); // may throw blind

// after — fail with context before calling
use Grav\Common\Filesystem\Folder;

$dir = GRAV_ROOT . '/cache/compiled/pages';
$parent = dirname($dir);
if (!is_dir($dir) && !is_writable($parent)) {
    throw new RuntimeException(sprintf('Cannot create %s: parent %s not writable by UID %d', $dir, $parent, posix_geteuid()));
}
Folder::create($dir);
Defensive patterns

Strategy: validation

Validate before calling

if (!is_dir($folder)) {
    $parent = dirname($folder);
    if (!is_dir($parent) || !is_writable($parent)) {
        throw new RuntimeException(sprintf('%s is not writable by UID %d — cannot create %s', $parent, posix_geteuid(), $folder));
    }
}
Folder::create($folder);

Try / catch

try {
    Folder::create($folder);
} catch (RuntimeException $e) {
    // check open_basedir, existing file at $folder, disk space, parent perms
    $log->error($e->getMessage());
    throw;
}

Prevention

When it happens

Trigger: Parent directory not writable by the PHP user (cache/, user/ under www-data while PHP runs as another UID); open_basedir excluding an ancestor of $folder; a regular file already exists at the exact $folder path; disk full or inode exhaustion; read-only filesystem mounts.

Common situations: Fresh installs where storage/ or cache/ were not chowned to the web user; hardening php.ini tightening open_basedir after the site worked; deploying with a Docker volume mounted read-only; permission-fix scripts that created files where directories were expected.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/c59876f00b126271. Report an issue: GitHub.