getgrav/grav · error · RuntimeException
Unable to %s: %s
Error message
Unable to %s: %s
What it means
Folder::delete() recursively removes a directory via doDelete(), which suppresses per-path PHP errors but records the first failing operation. When deletion fails, the precise reason captured by deleteFailure() is thrown: 'Unable to delete file|remove directory|remove symlink: /path (OS reason)' plus an ownership hint like [owned by "www-data", but this process runs as "cli"] when POSIX data shows a mismatch. The message exists to pinpoint exactly which path failed and why, instead of a bare 'Permission denied'.
Source
Thrown at system/src/Grav/Common/Filesystem/Folder.php:430
* @param bool $include_target
* @return bool
* @throws RuntimeException
*/
public static function delete($target, $include_target = true)
{
if (!is_dir($target)) {
return false;
}
$failure = null;
$success = self::doDelete($target, $include_target, $failure);
if (!$success) {
// Prefer the precise reason captured at the first failing path
// (which file/dir, the OS error, and any owner/process mismatch).
// Fall back to the last PHP error, then to a generic message.
if (null !== $failure) {
throw new RuntimeException($failure);
}
$error = error_get_last();
throw new RuntimeException($error['message'] ?? 'Unknown error');
}
// Make sure that the change will be detected when caching.
if ($include_target) {
@touch(dirname($target));
} else {
@touch($target);
}
return $success;
}
/**View on GitHub (pinned to 6040efed04)
Solutions
- Read the message: it names the failing path, the OS error, and any owner-vs-process mismatch — fix that exact path first
- Align ownership: chown -R <webuser>:<webgroup> cache/ logs/ images/ tmp/ backup/ (or run the CLI as the same user, e.g. sudo -u www-data bin/grav clear-cache)
- Loosen permissions only as needed: find cache -type d -exec chmod 755 {} + and files 644, avoiding 0777
- On NFS/Windows, ensure no process holds files open and the mount permits the operating user to unlink
Example fix
# before — cron runs as root, files owned by www-data
0 3 * * * php /var/www/grav/bin/grav clear-cache # throws 'Unable to delete file: ... (Permission denied)'
# after — run as the web user
0 3 * * * sudo -u www-data php /var/www/grav/bin/grav clear-cache
# or fix ownership once:
chown -R www-data:www-data /var/www/grav/{cache,logs,tmp,images} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: can this process remove everything under the target?
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($target, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if (!is_writable($file->getPathname())) {
throw new RuntimeException('Not removable (perms): ' . $file->getPathname());
break;
}
} Try / catch
try {
Folder::delete($dir);
} catch (RuntimeException $e) {
// message names the path, OS reason, and owner/process mismatch — log it whole
$log->warning('Cache purge failed: ' . $e->getMessage());
// safe to retry once after chown/chmod, otherwise report
} Prevention
- Run CLI tasks (clear-cache, scheduler) as the same user the web server runs as (sudo -u www-data)
- chown the writable tree (cache/, logs/, tmp/, images/, backup/) to the web user at deploy time
- Monitor backup/cron output so the first 'Permission denied' is fixed before files accumulate
When it happens
Trigger: Running bin/grav clear-cache or scheduled tasks as a different user than the web server that owns cache//logs/ files; a read-only or full filesystem; files held open/locked (common on Windows or NFS); a symlink whose unlink fails; safe_mode-style permission bits (0444 dirs, immutable flags) preventing rmdir/unlink.
Common situations: Cache dirs created by www-data but cleared from cron as root or vice versa; containerized deployments with mismatched UID/GID between PHP-FPM and CLI; migrated sites preserving old owner IDs; NFS/SMB mounts with root-squash semantics.
Related errors
- Unknown error
- Unable to create directory: %s
- Creating directory failed for {filepath}
- Opening file for writing failed on error {$message}
- Failed to commit nonce key file
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/350a847df0ea6604.
Report an issue: GitHub.