eyaltoledano/claude-task-master · error · Error
Failed to delete file ${filePath}: ${error.message}
Error message
Failed to delete file ${filePath}: ${error.message} What it means
deleteFile() calls fs.unlink and swallows ENOENT (deleting a non-existent file is treated as success), but re-throws any other failure as 'Failed to delete file <path>: <reason>'. This is used for tag deletion cleanup of tag-scoped files.
Source
Thrown at packages/tm-core/src/modules/storage/adapters/file-storage/file-operations.ts:246
async ensureDir(dirPath: string): Promise<void> {
try {
await fs.mkdir(dirPath, { recursive: true });
} catch (error: any) {
throw new Error(
`Failed to create directory ${dirPath}: ${error.message}`
);
}
}
/**
* Delete file
*/
async deleteFile(filePath: string): Promise<void> {
try {
await fs.unlink(filePath);
} catch (error: any) {
if (error.code !== 'ENOENT') {
throw new Error(`Failed to delete file ${filePath}: ${error.message}`);
}
}
}
/**
* Rename/move file
*/
async moveFile(oldPath: string, newPath: string): Promise<void> {
try {
await fs.rename(oldPath, newPath);
} catch (error: any) {
throw new Error(
`Failed to move file from ${oldPath} to ${newPath}: ${error.message}`
);
}
}
/**View on GitHub (pinned to c0c98d367c)
Solutions
- Check the OS error in the message and fix accordingly: grant write permission on the file's parent directory (deletion requires directory write access).
- Close other processes holding the file (editors, sync/backup tools) and retry.
- If the path is a directory rather than a file, remove it with the appropriate recursive removal instead.
- Note ENOENT is already tolerated — if you get this error the file exists but is unremovable, so no existence check is needed.
Example fix
// before rm .taskmaster/tags/feature.json # rm: permission denied // after sudo chown -R $(whoami) .taskmaster chmod -R u+w .taskmaster
Defensive patterns
Strategy: fallback
Validate before calling
import { stat, access, constants } from 'fs/promises';
import path from 'path';
export async function canDeleteFile(filePath: string): Promise<boolean> {
try {
const s = await stat(filePath);
if (!s.isFile()) return false;
await access(path.dirname(filePath), constants.W_OK); // dir write permission required to unlink
return true;
} catch { return false; }
} Try / catch
try {
await fileOps.deleteFile(filePath);
} catch (err: any) {
if (err.message.startsWith('Failed to delete file')) {
// ENOENT is already tolerated by the library; this is EACCES/EBUSY/etc.
console.warn(`Could not delete ${filePath} (${err.message}); leaving file in place.`);
return; // treat as non-fatal for tag cleanup
}
throw err;
} Prevention
- Grant write permission on the .taskmaster directory (unlink requires directory write access).
- Close editors/sync tools (Dropbox, OneDrive, antivirus) that hold files open on Windows.
- Confirm the path is a file, not a directory, before deletion flows.
- Treat cleanup deletions as best-effort with warnings rather than hard failures.
When it happens
Trigger: deleteTag (via deleteFile) attempting to unlink a file that exists but cannot be removed: EACCES/EPERM on the file or its directory, EBUSY (file open/locked on Windows), EISDIR (path is a directory), or read-only filesystem (EROFS).
Common situations: Running as a user without write permission on the .taskmaster directory; another process (editor, sync client like Dropbox) holding the file open on Windows; the path actually being a directory; deleting from a read-only mounted volume.
Related errors
- Failed to read file ${filePath}: ${error.message}
- Failed to read ${filePath} for modification: ${err.message}
- Failed to create directory ${dirPath}: ${error.message}
- CONFIG_ERROR
- Failed to move file from ${oldPath} to ${newPath}: ${error.m
AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29).
Data as JSON: /api/errors/a979a1afd6998855.
Report an issue: GitHub.