# How DesktopCommanderMCP's ConfigManager Serializes Writes to Prevent Corruption

> DesktopCommanderMCP's ConfigManager serializes writes via a promise chain, preventing file corruption by ensuring single-threaded disk operations for reliable data handling.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-07-13

---

**The `ConfigManager` prevents file corruption by serializing all disk writes through a single promise chain (`writeChain`), ensuring that only one write operation executes at a time regardless of how many concurrent updates are requested.**

The DesktopCommanderMCP tool relies on a robust configuration system to persist user settings across sessions. When multiple processes or rapid successive calls attempt to update [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) simultaneously, race conditions could corrupt the file. The `ConfigManager` class in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) eliminates this risk by implementing a serialized write pipeline that guarantees atomic, ordered persistence.

## The Promise Chain Architecture

The core serialization mechanism is a private `writeChain` property that functions as a write queue. Rather than allowing independent `fs.writeFile` calls to compete for disk access, every persistence operation appends itself to this chain, creating a strict happens-before relationship between updates.

### Initializing the Write Queue

When the manager is constructed, it initializes the chain with a resolved promise. This provides a non-blocking starting point onto which subsequent writes can be attached.

In [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) (lines 55-58):

```typescript
constructor() {
  // Initialize write chain for serializing config writes
  this.writeChain = Promise.resolve();
  // ... initialization logic
}

```

### Appending Operations to the Chain

Every call to `saveConfig()` extends the chain by reassigning `this.writeChain` to a new promise that awaits the previous link before executing the disk write. This pattern ensures that even if `saveConfig()` is invoked multiple times in rapid succession, the underlying `writeConfigToDisk()` method executes strictly sequentially.

According to lines 94-99 in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts):

```typescript
async saveConfig(): Promise<void> {
  // Chain writes to prevent corruption from concurrent updates
  this.writeChain = this.writeChain.then(async () => {
    await this.writeConfigToDisk();
  });
  return this.writeChain;
}

```

Because JavaScript promises execute in microtask order and the chain awaits each link, the next write cannot begin until the previous file operation completes.

## Coalesced Background Saves with scheduleSave

For high-frequency, non-critical updates—such as usage statistics or telemetry flags—the manager provides `setValueNonBlocking()`, which leverages a coalescing mechanism to reduce I/O pressure while maintaining serialization guarantees.

### The saveScheduled Flag

The `scheduleSave()` method uses a boolean flag to prevent queuing redundant writes while one is already in flight. When the flag is `true`, subsequent calls exit immediately, knowing that the pending write will persist the current in-memory state.

As implemented in lines 109-112:

```typescript
private scheduleSave(): void {
  if (this.saveScheduled) return; // Already queued
  this.saveScheduled = true;
  // ...
}

```

### Coalescing Logic

Once the flag is set, the method appends a deferred save to the promise chain. When that save completes, the flag resets, allowing the next burst of updates to trigger a single new write. This ensures that a "storm" of rapid updates—such as hundreds of rapid tool invocations—results in only one disk operation per batch rather than hundreds of overlapping writes.

From lines 113-119:

```typescript
this.writeChain = this.writeChain.then(async () => {
  if (this.saveScheduled) {
    await this.writeConfigToDisk();
    this.saveScheduled = false;
  }
});

```

## Atomic Disk Writes via writeConfigToDisk

All persistence paths converge on the private `writeConfigToDisk()` method, which performs a single atomic file replacement. Because the promise chain guarantees that only one invocation of this method runs at any moment, the file is never left in a partially-written state.

In [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) (lines 86-88):

```typescript
private async writeConfigToDisk(): Promise<void> {
  await fs.writeFile(
    this.configPath, 
    JSON.stringify(this.config, null, 2), 
    'utf8'
  );
}

```

The `fs.writeFile` call serializes the entire `config` object to JSON in one operation, and because the surrounding promise chain eliminates concurrency, no other process can interleave writes between the start and completion of this call.

## Public API Patterns

The manager exposes two distinct persistence patterns, allowing callers to choose the appropriate consistency model for their use case.

### Immediate Blocking Persistence

Use `setValue()` when the update must be durably persisted before the operation proceeds. This method updates the in-memory configuration and awaits `saveConfig()`, blocking the caller until the data is safely on disk.

```typescript
// Blocking call ensures durability before proceeding
await configManager.setValue('defaultShell', '/bin/bash');

```

### Non-Blocking Coalesced Updates

Use `setValueNonBlocking()` for batched or statistical updates where immediate durability is less critical than performance. This updates the in-memory state immediately and triggers `scheduleSave()`, delegating the actual disk write to the background chain.

```typescript
// Returns immediately; write happens asynchronously in the chain
configManager.setValueNonBlocking('lastUsedTool', 'git');
configManager.setValueNonBlocking('sessionCount', 42);
// Both changes coalesce into a single disk write

```

## Summary

- The **`writeChain`** promise queue ensures strict, sequential ordering of all disk writes, preventing race conditions where multiple updates could interleave.
- **`scheduleSave()`** coalesces rapid, non-blocking updates into single I/O operations using the `saveScheduled` flag, reducing disk pressure while maintaining the serialization guarantee.
- **`writeConfigToDisk()`** performs atomic JSON serialization through a single `fs.writeFile` call, ensuring that the configuration file is never left in a partially-written state.
- Callers choose between **immediate consistency** via `setValue()` or **deferred persistence** via `setValueNonBlocking()` based on their durability requirements.

## Frequently Asked Questions

### What prevents two simultaneous calls from corrupting config.json?

The `writeChain` promise chain ensures that only one `writeConfigToDisk()` operation executes at a time. Even if `saveConfig()` is called concurrently from multiple tool invocations, each call appends its write operation to the end of the chain. Because JavaScript promises resolve sequentially within the chain, the next write cannot begin until the previous one has fully committed to disk, eliminating interleaving corruption.

### How does scheduleSave reduce I/O pressure during high-frequency updates?

The `scheduleSave()` method checks the `saveScheduled` boolean flag before queuing a new write. If a write is already pending, subsequent calls return immediately without extending the chain further. Once the pending write completes and clears the flag, the next batch of updates triggers a single new write. This coalescing ensures that hundreds of rapid updates—such as telemetry increments—result in only a few actual disk operations rather than hundreds of competing file writes.

### Is the write operation truly atomic?

While `fs.writeFile` itself is not guaranteed to be atomic at the operating system level, the serialization mechanism guarantees *logical atomicity* from the perspective of the ConfigManager. Because the promise chain ensures that no other code path can call `writeConfigToDisk()` while a previous call is in progress, the file is never exposed to readers or other writers in a partially-written state. The entire JSON string is prepared in memory before the single `writeFile` call begins.

### Where is the write-serialization logic implemented in the codebase?

All serialization logic resides in [[`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts). Specifically, the `writeChain` initialization appears at lines 55-58, the chain extension logic in `saveConfig()` is at lines 94-99, the coalescing `scheduleSave()` method is at lines 109-119, and the actual disk write in `writeConfigToDisk()` is at lines 86-88. The configuration file path is defined in [[`src/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts)](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts) as `CONFIG_FILE`.