CherryHQ/cherry-studio · error · Error

Failed to write file: ${error.message}

Error message

Failed to write file: ${error.message}

What it means

Thrown by the write tool when fs.writeFile(validPath, content, 'utf-8') rejects. By this point the parent directory was created (or already existed), so the failure is at the file-write itself: EACCES (no write permission on the file/path), EISDIR (target is a directory), ENOSPC (disk full), EROFS (read-only fs), EDQUOT (quota exceeded), or EBADF. The underlying message is appended verbatim.

Source

Thrown at src/main/ai/mcp/servers/filesystem/tools/write.ts:60

    if (error.code !== 'EEXIST') {
      throw new Error(`Failed to create parent directory: ${error.message}`)
    }
  }

  // Check if file exists (for logging)
  let isOverwrite = false
  try {
    await fs.stat(validPath)
    isOverwrite = true
  } catch {
    // File doesn't exist, that's fine
  }

  // Write the file
  try {
    await fs.writeFile(validPath, parsed.data.content, 'utf-8')
  } catch (error: any) {
    throw new Error(`Failed to write file: ${error.message}`)
  }

  // Log the operation
  logger.info('File written', {
    path: validPath,
    overwrite: isOverwrite,
    size: parsed.data.content.length
  })

  // Format output
  const relativePath = path.relative(baseDir, validPath)
  const action = isOverwrite ? 'Updated' : 'Created'
  const lines = parsed.data.content.split('\n').length

  return {
    content: [
      {
        type: 'text',

View on GitHub (pinned to 726446b54c)

Solutions

  1. Verify write permission on the target file/path (fs.access with W_OK) before calling write.
  2. Free disk space or raise the user quota if error.message mentions ENOSPC/EDQUOT.
  3. If the target is a directory, choose a file path instead (EISDIR).
  4. Handle the race by catching EACCES/EISDIR and reporting the resolved path to the user.

Example fix

// before
await fs.chmod(target, 0o444)
await handleWriteTool({ file_path: target, content: 'x' }, baseDir) // throws: Failed to write file (EACCES)

// after
await fs.chmod(target, 0o644)
await handleWriteTool({ file_path: target, content: 'x' }, baseDir)
Defensive patterns

Strategy: try-catch

Validate before calling

import fs from 'fs/promises'
async function canWrite(p: string): Promise<void> {
  await fs.access(path.dirname(p), fs.constants.W_OK)
  try {
    const s = await fs.stat(p)
    if (!s.isFile()) throw new Error(`Target is not a regular file: ${p}`)
    await fs.access(p, fs.constants.W_OK)
  } catch (e: any) { if (e.code !== 'ENOENT') throw e }
}

Try / catch

try {
  await handleWriteTool(args, baseDir)
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e)
  if (msg.startsWith('Failed to write file')) {
    // EACCES -> chmod/ownership, ENOSPC -> free space, EISDIR -> pick a file path
  } else throw e
}

Prevention

When it happens

Trigger: Overwriting a read-only or permission-locked file; writing where validPath resolves to a directory (EISDIR, despite the earlier stat-based isOverwrite check racing); disk full mid-write; quota exceeded; file deleted between the stat and writeFile.

Common situations: CI/container with a read-only output layer; file owned by another user with mode 0444; large content exceeding disk quota; concurrent process removed the file after the stat check; NFS/SMB mount with delayed permissions.

Related errors


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