yiisoft/yii2 · error · yii\base\InvalidArgumentException
Unable to change ownership, "{$path}" is not a file or direc
Error message
Unable to change ownership, "{$path}" is not a file or directory. What it means
FileHelper::changeOwnership() validates its target before touching anything: file_exists((string)$path) is the very first check, and a path that does not exist throws InvalidArgumentException. All later steps (chmod/chown/chgrp) assume a real filesystem node, so the guard prevents operating on typo'd paths, deleted files, or relative paths that resolved from the wrong working directory.
Source
Thrown at framework/helpers/BaseFileHelper.php:976
* @param string $path the path to the file or directory.
* @param string|array|int|null $ownership the user and/or group ownership for the file or directory.
* When $ownership is a string, the format is 'user:group' where both are optional. E.g.
* 'user' or 'user:' will only change the user,
* ':group' will only change the group,
* 'user:group' will change both.
* When $owners is an index array the format is [0 => user, 1 => group], e.g. `[$myUser, $myGroup]`.
* It is also possible to pass an associative array, e.g. ['user' => $myUser, 'group' => $myGroup].
* In case $owners is an integer it will be used as user id.
* If `null`, an empty array or an empty string is passed, the ownership will not be changed.
* @param int|null $mode the permission to be set for the file or directory.
* If `null` is passed, the mode will not be changed.
*
* @since 2.0.43
*/
public static function changeOwnership($path, $ownership, $mode = null)
{
if (!file_exists((string)$path)) {
throw new InvalidArgumentException('Unable to change ownership, "' . $path . '" is not a file or directory.');
}
if (empty($ownership) && $ownership !== 0 && $mode === null) {
return;
}
$user = $group = null;
if (!empty($ownership) || $ownership === 0 || $ownership === '0') {
if (is_int($ownership)) {
$user = $ownership;
} elseif (is_string($ownership)) {
$ownerParts = explode(':', $ownership);
$user = $ownerParts[0];
if (count($ownerParts) > 1) {
$group = $ownerParts[1];
}
} elseif (is_array($ownership)) {
$ownershipIsIndexed = ArrayHelper::isIndexed($ownership);View on GitHub (pinned to 66f00d18a2)
Solutions
- Check file_exists($path) (or is_file/is_dir for stricter intent) immediately before the call.
- Build paths with Yii::getAlias() and rtrim($base, '/') . '/' . $name instead of bare concatenation.
- Create or move the file first, then change ownership as a separate final step.
- If the file is optional, skip the call rather than letting it throw.
Example fix
// before
\yii\helpers\FileHelper::changeOwnership($tmpPath, 'www-data:www-data', 0644);
// after
if (is_file($tmpPath)) {
\yii\helpers\FileHelper::changeOwnership($tmpPath, 'www-data:www-data', 0644);
} Defensive patterns
Strategy: validation
Validate before calling
if (!file_exists($path)) {
throw new \RuntimeException("Cannot change ownership — path does not exist: {$path}");
}
\yii\helpers\FileHelper::changeOwnership($path, 'www-data:www-data', 0644); Try / catch
try {
\yii\helpers\FileHelper::changeOwnership($path, $ownership, $mode);
} catch (\yii\base\InvalidArgumentException $e) {
\Yii::warning("changeOwnership rejected: {$e->getMessage()}", 'files');
} Prevention
- Build paths with Yii::getAlias() and explicit separators instead of concatenation.
- Change ownership as the final step of a file-creation pipeline, after the file is guaranteed to exist.
- Skip optional files explicitly rather than relying on the exception as control flow.
When it happens
Trigger: changeOwnership('/srv/app/missing.txt', 'www-data') for a path with a typo or missing separator; a temp file already moved/deleted by another handler before the call; a relative path resolved against CLI cwd instead of the app root; a race with a cleanup job that removed the file.
Common situations: Post-upload permission fixups where the uploaded file was renamed/moved by a previous step; deployment scripts referencing files not yet created; paths built by naive concatenation ('/var/www' . 'app.css'); running the same code in different containers with different volume layouts.
Related errors
- Failed to change permissions for directory "{$path}": {messa
- FileDependency::fileName must be set
- Unable to open directory: $dir
- The dir argument must be a directory: $dir
- If exclude/include pattern is an array it must contain the p
AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17).
Data as JSON: /api/errors/bc2889ba350ec74c.
Report an issue: GitHub.