CherryHQ/cherry-studio · error · Error

Cannot read binary file: ${filePath}

Error message

Cannot read binary file: ${filePath}

What it means

Thrown by the read tool after isBinaryFile(validPath) returns true. isBinaryFile (types.ts:557) reads the first 4096 bytes and applies a heuristic: >5% null bytes (unless the distribution looks like UTF-16), or >30% non-printable bytes outside the ASCII/whitespace/high-byte ranges. The read tool reads with 'utf-8' encoding, so binary content would corrupt output and is rejected upfront.

Source

Thrown at src/main/ai/mcp/servers/filesystem/tools/read.ts:55

  const filePath = parsed.data.file_path
  const validPath = await validatePath(filePath, baseDir)

  // Check if file exists
  try {
    const stats = await fs.stat(validPath)
    if (!stats.isFile()) {
      throw new Error(`Path is not a file: ${filePath}`)
    }
  } catch (error: any) {
    if (error.code === 'ENOENT') {
      throw new Error(`File not found: ${filePath}`)
    }
    throw error
  }

  // Check if file is binary
  if (await isBinaryFile(validPath)) {
    throw new Error(`Cannot read binary file: ${filePath}`)
  }

  // Read file content
  const content = await fs.readFile(validPath, 'utf-8')
  const lines = content.split('\n')

  // Apply offset and limit
  const offset = (parsed.data.offset || 1) - 1 // Convert to 0-based
  const limit = parsed.data.limit || DEFAULT_READ_LIMIT

  if (offset < 0 || offset >= lines.length) {
    throw new Error(`Invalid offset: ${offset + 1}. File has ${lines.length} lines.`)
  }

  const selectedLines = lines.slice(offset, offset + limit)

  // Format output with line numbers and truncate long lines
  const output: string[] = []

View on GitHub (pinned to 726446b54c)

Solutions

  1. Do not use the text read tool for binary assets; use a dedicated binary reader or stream the file directly.
  2. If the file is genuinely text but misdetected (e.g. UTF-16 without BOM), convert it to UTF-8 first.
  3. For borderline files, confirm with the `file` command or by inspecting the first bytes before calling read.

Example fix

// before
await handleReadTool({ file_path: 'assets/logo.png' }, baseDir) // throws: Cannot read binary file

// after
import fs from 'fs/promises'
const buf = await fs.readFile(path.join(baseDir, 'assets/logo.png')) // handle as binary
Defensive patterns

Strategy: validation

Validate before calling

import { isBinaryFile } from '../types'
async function safeRead(p: string) {
  if (await isBinaryFile(p)) throw new Error(`Refusing binary: ${p}`)
  // proceed with text read
}

Try / catch

try {
  await handleReadTool(args, baseDir)
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Cannot read binary file')) {
    // route to a binary handler instead
  } else throw e
}

Prevention

When it happens

Trigger: Calling handleReadTool on an executable, image, archive, compiled object, database file, or any file whose first 4KB contains null bytes or many non-printable bytes. The path is valid, exists, and is a regular file, so all earlier checks pass.

Common situations: Reading assets (png/jpg/wasm/so/dll) that happen to live in the workspace; reading a SQLite .db or pickle file; a UTF-16 file whose zero-byte pattern does not trip the UTF-16 escape ratio but exceeds the 5% threshold; large binary blobs with no extension.

Related errors


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