{"record":{"id":"f61451c28770991c","repo":"CherryHQ/cherry-studio","slug":"cannot-read-binary-file-filepath","errorCode":null,"errorMessage":"Cannot read binary file: ${filePath}","messagePattern":"Cannot read binary file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/filesystem/tools/read.ts","lineNumber":55,"sourceCode":"  const filePath = parsed.data.file_path\n  const validPath = await validatePath(filePath, baseDir)\n\n  // Check if file exists\n  try {\n    const stats = await fs.stat(validPath)\n    if (!stats.isFile()) {\n      throw new Error(`Path is not a file: ${filePath}`)\n    }\n  } catch (error: any) {\n    if (error.code === 'ENOENT') {\n      throw new Error(`File not found: ${filePath}`)\n    }\n    throw error\n  }\n\n  // Check if file is binary\n  if (await isBinaryFile(validPath)) {\n    throw new Error(`Cannot read binary file: ${filePath}`)\n  }\n\n  // Read file content\n  const content = await fs.readFile(validPath, 'utf-8')\n  const lines = content.split('\\n')\n\n  // Apply offset and limit\n  const offset = (parsed.data.offset || 1) - 1 // Convert to 0-based\n  const limit = parsed.data.limit || DEFAULT_READ_LIMIT\n\n  if (offset < 0 || offset >= lines.length) {\n    throw new Error(`Invalid offset: ${offset + 1}. File has ${lines.length} lines.`)\n  }\n\n  const selectedLines = lines.slice(offset, offset + limit)\n\n  // Format output with line numbers and truncate long lines\n  const output: string[] = []","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/filesystem/tools/read.ts#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Do not use the text read tool for binary assets; use a dedicated binary reader or stream the file directly.","If the file is genuinely text but misdetected (e.g. UTF-16 without BOM), convert it to UTF-8 first.","For borderline files, confirm with the `file` command or by inspecting the first bytes before calling read."],"exampleFix":"// before\nawait handleReadTool({ file_path: 'assets/logo.png' }, baseDir) // throws: Cannot read binary file\n\n// after\nimport fs from 'fs/promises'\nconst buf = await fs.readFile(path.join(baseDir, 'assets/logo.png')) // handle as binary","handlingStrategy":"validation","validationCode":"import { isBinaryFile } from '../types'\nasync function safeRead(p: string) {\n  if (await isBinaryFile(p)) throw new Error(`Refusing binary: ${p}`)\n  // proceed with text read\n}","typeGuard":null,"tryCatchPattern":"try {\n  await handleReadTool(args, baseDir)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Cannot read binary file')) {\n    // route to a binary handler instead\n  } else throw e\n}","preventionTips":["Maintain an allowlist of text extensions (.ts/.js/.md/.json/.txt) and reject the rest before reading.","For assets, use a dedicated binary read path rather than the text read tool.","Convert UTF-16 or other encoded text files to UTF-8 before they enter the workspace."],"tags":["filesystem","validation","mcp","read-tool","binary"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}