yiisoft/yii2 · error · yii\base\Exception

Failed to change permissions for directory "{$path}": {messa

Error message

Failed to change permissions for directory "{$path}": {message}

What it means

Thrown by yii\helpers\BaseFileHelper::createDirectory() when the directory was created (or already existed) but the subsequent native chmod($path, $mode) call raised an exception. Yii re-throws it as yii\base\Exception with chmod's message. Native chmod() normally only warns and returns false, so the throw is reached when an error handler converts the E_WARNING to an ErrorException (common under Yii's ErrorHandler or strict custom handlers) or when PHP 8 raises a ValueError/TypeError for an invalid $mode argument.

Source

Thrown at framework/helpers/BaseFileHelper.php:726

        }
        $parentDir = dirname($path);
        // recurse if parent dir does not exist and we are not at the root of the file system.
        if ($recursive && !is_dir($parentDir) && $parentDir !== $path) {
            static::createDirectory($parentDir, $mode, true);
        }
        try {
            if (!mkdir($path, $mode)) {
                return false;
            }
        } catch (\Exception $e) {
            if (!is_dir($path)) {// https://github.com/yiisoft/yii2/issues/9288
                throw new \yii\base\Exception("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
            }
        }
        try {
            return chmod($path, $mode);
        } catch (\Exception $e) {
            throw new \yii\base\Exception("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * Performs a simple comparison of file or directory names.
     *
     * Based on match_basename() from dir.c of git 1.8.5.3 sources.
     *
     * @param string $baseName file or directory name to compare with the pattern
     * @param string $pattern the pattern that $baseName will be compared against
     * @param int|bool $firstWildcard location of first wildcard character in the $pattern
     * @param int $flags pattern flags
     * @return bool whether the name matches against pattern
     */
    private static function matchBasename($baseName, $pattern, $firstWildcard, $flags)
    {
        if ($firstWildcard === false) {
            if ($pattern === $baseName) {

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Fix ownership so the PHP process owns the directory it must chmod: sudo chown -R www-data:www-data <dir> (only the owner may chmod), then retry createDirectory.
  2. Pass the mode as an octal integer literal (0775), never a string ('0775') — on PHP 8 the wrong type raises ValueError/TypeError before chmod runs.
  3. If the directory already exists with correct permissions, skip the mode-enforcing call: guard with if (!is_dir($path)) { FileHelper::createDirectory($path, $mode); } since the chmod branch only runs after a fresh mkdir.
  4. On NFS/ACL/immutable filesystems, adjust the export options (no_root_squash for service accounts), clear chattr +i, or pre-create the directory with the desired mode from the owning side.
  5. If a global set_error_handler converts every E_WARNING to exceptions, exempt filesystem functions or wrap createDirectory in a handler that tolerates chmod warnings when is_dir($path) already holds.

Example fix

// before
FileHelper::createDirectory($cachePath, '0777'); // string mode -> PHP 8 ValueError caught and re-thrown here

// after
$mode = 0775; // octal int
if (!is_dir($cachePath)) {
    FileHelper::createDirectory($cachePath, $mode);
}
Defensive patterns

Strategy: validation

Validate before calling

$mode = 0775; // octal int, never a string
if (!is_int($mode) || $mode < 0 || $mode > 07777) {
    throw new InvalidArgumentException('mode must be an octal int like 0775');
}
if (is_dir($path)) {
    $owner = fileowner($path);
    $current = posix_geteuid();
    if ($owner !== false && $current !== false && $owner !== $current && !function_exists('posix_geteuid') === false && $owner !== $current) {
        // chmod will fail: only the owner may change mode
        clearstatcache(true, $path);
        if (fileowner($path) !== $current) {
            Yii::warning("Cannot chmod $path: owned by uid " . fileowner($path) . ", running as $current");
        }
    }
} else {
    $parent = dirname($path);
    while (!is_dir($parent)) { $parent = dirname($parent); }
    if (!is_writable($parent)) {
        throw new RuntimeException("Parent $parent not writable; mkdir would fail first");
    }
}

Type guard

function modeIsSafeForChmod(int|string $mode): bool
{
    return is_int($mode) && $mode >= 0 && $mode <= 07777;
}

function directoryOwnedByCurrentProcess(string $path): bool
{
    clearstatcache(true, $path);
    return is_dir($path) && fileowner($path) === posix_geteuid(); // false => chmod() cannot succeed
}

Try / catch

try {
    FileHelper::createDirectory($path, 0775, true);
} catch (\yii\base\Exception $e) {
    if (strpos($e->getMessage(), 'Failed to change permissions') === 0 && is_dir($path)) {
        // Directory exists; only the mode could not be enforced (owner mismatch / ACL).
        Yii::warning("{$path} created but mode not applied: {$e->getMessage()}", __METHOD__);
    } else {
        throw $e; // creation itself failed — do not swallow
    }
}

Prevention

When it happens

Trigger: Calling FileHelper::createDirectory($path, $mode) when: the directory exists but is owned by a different user than the PHP process (chmod only allowed for the owner); the filesystem is NFS/root-squash, a read-only bind mount, or has restrictive ACLs/immutable attributes; $mode is passed as a non-octal string like '0777' or an invalid int on PHP 8 (ValueError); or a set_error_handler turns chmod()'s warning into ErrorException.

Common situations: A deploy pipeline or cron job pre-creates runtime/cache dirs as root, then php-fpm as www-data cannot chmod them; NFS shares with root_squash mapping the web user to nobody; directories with the immutable flag (chattr +i) from hardening; developers passing the mode as a string '0775' instead of the octal literal 0775; upgrades to PHP 8 surfacing argument type errors that PHP 7 ignored.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/d58328d3d6a0603f. Report an issue: GitHub.