Understanding fileWriteLineLimit in DesktopCommanderMCP: Why Chunked Writes Are Enforced

The fileWriteLineLimit in DesktopCommanderMCP is a 10 MiB threshold (LINE_COUNT_LIMIT) that triggers chunked writes to prevent UI-blocking I/O, reduce memory pressure, and improve reliability when handling large files.

The DesktopCommanderMCP codebase implements protective thresholds for file operations to ensure smooth performance even when dealing with massive text files. These safeguards are particularly important for a desktop application where unresponsive UI directly impacts user experience.

What Is fileWriteLineLimit?

The term refers to a pair of constants defined in src/utils/files/text.ts that govern how the system treats large files:

Constant Value Purpose
FILE_SIZE_LIMITS.LARGE_FILE_THRESHOLD 10 MiB Files exceeding this size receive special handling (line counting disabled, streaming reads).
FILE_SIZE_LIMITS.LINE_COUNT_LIMIT 10 MiB Same threshold used specifically for the line-counting decision boundary.

When a write request arrives, the TextFileHandler.write method checks these limits. For files under the threshold, it can safely perform operations like line counting. For larger files, the system bypasses expensive computations and delegates to streaming write methods.

// Located in src/utils/files/text.ts
export const FILE_SIZE_LIMITS = {
  LARGE_FILE_THRESHOLD: 10 * 1024 * 1024, // 10 MiB
  LINE_COUNT_LIMIT: 10 * 1024 * 1024,     // 10 MiB
} as const;

The write method itself uses Node's fs.appendFile or fs.writeFile without loading the entire target file into memory, regardless of file size. This streaming approach is the first layer of protection.

Why Force Chunked Writes?

The DesktopCommanderMCP architecture enforces chunked writes through a secondary mechanism in src/utils/toolHistory.ts. This is not optional for high-frequency or large-payload operations—it's baked into the tool-history recording system for three critical reasons:

Prevents UI-Blocking I/O

A single synchronous writeFileSync call with a massive payload stalls the Node.js event loop. In a desktop application, this manifests as frozen UI, unresponsive menus, and a degraded user experience. The chunked approach yields control back to the event loop between chunks.

Reduces Memory Pressure

Instead of buffering an entire large file or massive write payload in RAM, the system maintains only a modest chunk size in memory at any moment. This is essential for long-running desktop sessions where memory leaks or spikes cause system-wide slowdowns.

Improves Reliability

Intermittent failures—disk full, permission denied, network drive disconnect—affect only the current chunk. The remaining data can be retried or logged without total data loss.

How Chunked Writes Are Implemented

The implementation lives in src/utils/toolHistory.ts. The ToolCallHistory class maintains a writeQueue array and processes it on a ~100 ms timer:

// Conceptual structure from src/utils/toolHistory.ts
class ToolCallHistory {
  private writeQueue: string[] = [];
  private flushTimer: NodeJS.Timeout | null = null;
  
  record(entry: ToolCallEntry): void {
    this.writeQueue.push(JSON.stringify(entry));
    this.scheduleFlush();
  }
  
  private scheduleFlush(): void {
    if (this.flushTimer) return;
    this.flushTimer = setTimeout(() => this.flushPendingWrites(), 100);
  }
  
  private async flushPendingWrites(): Promise<void> {
    const batch = this.writeQueue.splice(0, this.writeQueue.length);
    // Writes are batched and streamed to disk
    await this.appendChunked(batch.join('\n'));
    this.flushTimer = null;
  }
  
  async gracefulShutdown(): Promise<void> {
    if (this.flushTimer) clearTimeout(this.flushTimer);
    await this.flushPendingWrites(); // Ensure no data loss
  }
}

Key properties of this implementation:

  • Batched small writes – Multiple rapid calls to record() collapse into a single disk operation.
  • Graceful shutdown – The flushPendingWrites method guarantees no queued data is lost on process exit.
  • Timer-driven flushing – The ~100 ms delay balances latency against I/O efficiency.

Using the Write Methods

Here's how to interact with the file-writing infrastructure correctly:

// Example 1: Simple write for small files
// src/utils/files/text.ts — TextFileHandler
import { TextFileHandler } from "./utils/files/text.js";

const handler = new TextFileHandler();
await handler.write("/tmp/config.json", jsonString, "rewrite");

// Example 2: Append mode for logs
await handler.write("/var/log/app.log", logEntry, "append");
// fs.appendFile is used under the hood — no full-file load
// Example 3: High-frequency writes — automatically chunked
// src/utils/toolHistory.ts — ToolCallHistory
import { ToolCallHistory } from "./utils/toolHistory.js";

const history = new ToolCallHistory("/home/user/.mcp/history.log");

// This queues internally; actual disk write may be deferred
history.record({
  tool: "edit_file",
  args: { path: "/tmp/large.txt", changes: hugeDiff },
  timestamp: Date.now()
});

// Or record multiple calls rapidly — they'll batch automatically
for (const change of massiveChangeSet) {
  history.record(change);
}

Contract Enforcement

All file handlers implement the FileHandler contract declared in src/utils/files/base.ts. This ensures that:

  • Every write operation respects the size thresholds.
  • Subclasses cannot accidentally bypass streaming methods.
  • The system can swap handlers (text, binary, etc.) without breaking limit guarantees.

Summary

  • fileWriteLineLimit is the 10 MiB LINE_COUNT_LIMIT constant in src/utils/files/text.ts.
  • Files below this threshold may have line counts computed; larger files skip this step.
  • Chunked writes are forced in src/utils/toolHistory.ts via a batched queue system (~100 ms flush interval).
  • The architecture prevents event-loop blocking, limits memory usage, and isolates failure domains.
  • The TextFileHandler.write method and ToolCallHistory.record method are the primary user-facing APIs.

Frequently Asked Questions

What happens if I try to write a file larger than 10 MiB?

The write proceeds, but the system disables expensive operations like line counting. In src/utils/files/text.ts, the write method still uses streaming fs.writeFile or fs.appendFile, so the operation succeeds without loading the entire file into memory. However, if you're using ToolCallHistory, the payload is automatically broken into chunks and queued.

Can I adjust the fileWriteLineLimit threshold?

The constants in FILE_SIZE_LIMITS are hard-coded in src/utils/files/text.ts. As implemented in wonderwhy-er/DesktopCommanderMCP, there is no runtime configuration option. Modifying the source and rebuilding would be required to change the 10 MiB limit.

Is chunked writing slower than a single large write?

Slightly, due to the ~100 ms batching delay in ToolCallHistory. However, this trade-off is intentional: the latency is imperceptible for human interactions, while the responsiveness and reliability gains are substantial. For TextFileHandler.write directly, there's no artificial delay—streams write as fast as the disk accepts data.

How do I ensure all queued writes complete before my script exits?

Call history.gracefulShutdown() (or equivalent flush method) on your ToolCallHistory instance. This clears the timer, executes flushPendingWrites(), and waits for the final disk operation to complete. Without this step, recent tool calls may remain in memory and never reach the log file.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →