CherryHQ/cherry-studio · warning · Error

Directory not empty: ${targetPath}. Use recursive=true to de

Error message

Directory not empty: ${targetPath}. Use recursive=true to delete non-empty directories.

What it means

fs.rmdir on a non-empty directory rejected with code ENOTEMPTY because the caller set recursive=false (or omitted it, defaulting to false) but the target directory still contains entries. The message tells the caller to pass recursive=true. Caught by the server-level handler and returned as an isError tool result.

Source

Thrown at src/main/ai/mcp/servers/filesystem/tools/delete.ts:69

  const relativePath = path.relative(baseDir, validPath)

  // Perform deletion
  try {
    if (isDirectory) {
      if (recursive) {
        // Delete directory recursively
        await fs.rm(validPath, { recursive: true, force: true })
      } else {
        // Try to delete empty directory
        await fs.rmdir(validPath)
      }
    } else {
      // Delete file
      await fs.unlink(validPath)
    }
  } catch (error: any) {
    if (error.code === 'ENOTEMPTY') {
      throw new Error(`Directory not empty: ${targetPath}. Use recursive=true to delete non-empty directories.`)
    }
    throw new Error(`Failed to delete: ${error.message}`)
  }

  // Log the operation
  logger.info('Path deleted', {
    path: validPath,
    type: isDirectory ? 'directory' : 'file',
    recursive: isDirectory ? recursive : undefined
  })

  // Format output
  const itemType = isDirectory ? 'Directory' : 'File'
  const recursiveNote = isDirectory && recursive ? ' (recursive)' : ''

  return {
    content: [
      {

View on GitHub (pinned to 726446b54c)

Solutions

  1. Re-invoke delete with recursive:true if intentional whole-tree removal is desired.
  2. If only an empty directory should be removed, first list it (ls tool) and confirm it has no entries, including hidden ones.
  3. Audit for hidden files like .DS_Store, .gitkeep, or editor temp files that make an 'empty' directory non-empty.

Example fix

// before
throw new Error(`Directory not empty: ${targetPath}. Use recursive=true to delete non-empty directories.`)

// after — include the entry count so the caller knows the scale
const entries = await fs.readdir(validPath)
throw new Error(`Directory not empty: ${targetPath} has ${entries.length} entries. Use recursive=true to delete.`)
Defensive patterns

Strategy: validation

Validate before calling

// Detect a non-empty directory before calling delete without recursive.
import { readdir } from 'fs/promises'
async function isDirEmpty(p: string): Promise<boolean> {
  const entries = await readdir(p)
  return entries.length === 0
}
if (stats.isDirectory() && !recursive) {
  const empty = await isDirEmpty(validPath)
  if (!empty) throw new Error('Pass recursive=true to delete non-empty directory')
}

Prevention

When it happens

Trigger: Calling delete on a directory that contains files or subdirectories without setting recursive:true. The handler first fs.stat'd the target as a directory, then chose fs.rmdir over fs.rm because the recursive flag was false.

Common situations: A model assuming delete is recursive by default; an empty-looking directory that still holds hidden files (e.g. .DS_Store) or a .git folder; a directory the user expected to be empty but contains nested output.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/9718ebb564a4ec4c. Report an issue: GitHub.