# How Desktop Commander Ensures Configuration Persistence with WriteChain Serialization

> Desktop Commander prevents config file corruption using writeChain serialization for ordered atomic disk writes and coalesced background saves.

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

---

**Desktop Commander prevents configuration file corruption by serializing all disk writes through a Promise-based writeChain that guarantees ordered, atomic operations while coalescing rapid updates into single background saves.**

DesktopCommanderMCP persists runtime settings in a JSON configuration file located in the user's home directory. To eliminate race conditions and corruption risks during concurrent tool operations, the `ConfigManager` class implements a sophisticated writeChain serialization mechanism. This architecture ensures that every configuration change is safely persisted to disk without interleaving bytes or blocking critical execution paths.

## The WriteChain Serialization Architecture

### Singleton ConfigManager Pattern

All tools share a single source of truth through the exported `configManager` singleton instance. Located in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) at lines 339-341, this pattern ensures that every read and write operation targets the same in-memory configuration object, preventing state divergence across the application.

```typescript
// src/config-manager.ts#L339-L341
export const configManager = new ConfigManager();

```

### The Promise Chain Guarantee

At the core of the safety mechanism lies the `writeChain` property, initialized as `Promise<void> = Promise.resolve()` at lines 55-56. This private field maintains a continuous chain of promises where each new write operation appends to the previous one using `.then()`. The implementation in `saveConfig()` (lines 105-110) creates a new link by assigning `const write = this.writeChain.then(() => this.writeConfigToDisk())`, ensuring that `fs.writeFile` calls execute sequentially rather than concurrently.

```typescript
// src/config-manager.ts#L55-L56
private writeChain: Promise<void> = Promise.resolve();

```

### Atomic File Operations

The actual persistence occurs in `writeConfigToDisk()`, which performs a single atomic write operation at lines 97-99. By serializing the entire configuration object in one operation and utilizing the writeChain to prevent overlapping calls, Desktop Commander eliminates partial write corruption risks.

```typescript
// src/config-manager.ts#L97-L99
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2), 'utf8');

```

## Blocking vs. Non-Blocking Configuration Updates

### Explicit Blocking Saves with `saveConfig()`

When immediate persistence is required, `saveConfig()` creates a blocking write operation that returns a promise resolving only after the file is safely on disk. This method updates `this.writeChain` with error handling via `write.catch(() => {})` to ensure that a single failed write does not break the chain for subsequent operations.

```typescript
// src/config-manager.ts#L105-L110
const write = this.writeChain.then(() => this.writeConfigToDisk());
this.writeChain = write.catch(() => {});
return write;

```

### Coalesced Background Saves with `scheduleSave()`

For high-frequency updates like usage statistics, `scheduleSave()` (lines 120-131) implements a coalescing mechanism. By checking `if (this.saveScheduled) return`, the method collapses rapid successive calls into a single background write. The implementation appends to the writeChain while allowing the caller to continue immediately.

```typescript
// src/config-manager.ts#L120-L131
if (this.saveScheduled) return;
this.saveScheduled = true;
this.writeChain = this.writeChain.then(async () => {
  this.saveScheduled = false;
  await this.writeConfigToDisk();
});

```

## First-Run Initialization Safety

During initial launch, the ConfigManager detects absent configuration files and executes a safe creation sequence. At lines 98-100, the code sets `this._isFirstRun = true` before invoking `await this.saveConfig()`, ensuring that the initial [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) is completely written before any tool attempts to read it.

```typescript
// src/config-manager.ts#L98-L100
this._isFirstRun = true;
await this.saveConfig();

```

## Practical Implementation Examples

### Updating Configuration with Blocking Persistence

Use `setValue()` when you need confirmation that the configuration is safely persisted before proceeding. This method calls `saveConfig()` internally, linking the operation to the writeChain.

```typescript
import { configManager } from './config-manager.js';

// Change the default shell and wait until the file is safely persisted.
await configManager.setValue('defaultShell', '/bin/bash');
console.log('Shell updated – config file is now on disk.');

```

### Updating Configuration Without Blocking

For telemetry or high-frequency statistics, use `setValueNonBlocking()` to update the in-memory state immediately while deferring the disk write. The background saver coalesces any rapid subsequent updates into a single file operation.

```typescript
import { configManager } from './config-manager.js';

// Record a usage statistic; the write happens in the background.
await configManager.setValueNonBlocking('lastCommand', 'ls -la');
// The caller continues immediately; rapid updates collapse into one disk write.

```

### Reading the Current Configuration

The `getConfig()` method ensures initialization is complete (creating the file on first run if necessary) before returning a shallow copy of the configuration object, preventing external mutations from bypassing the manager's controls.

```typescript
import { configManager } from './config-manager.js';

const cfg = await configManager.getConfig();
console.log('Current config version:', cfg.version);

```

## Summary

- The singleton `ConfigManager` exports a single `configManager` instance that serves as the exclusive interface for configuration state.
- A private `writeChain` Promise serializes all disk writes through `.then()` chaining, guaranteeing that `fs.writeFile` operations never overlap.
- Atomic writes in `writeConfigToDisk()` serialize the entire JSON object in one operation, preventing corruption from partial writes.
- `saveConfig()` provides blocking persistence for critical updates, while `scheduleSave()` coalesces rapid changes into efficient background writes.
- Error isolation via `.catch()` ensures that write failures do not break the chain, allowing subsequent configuration updates to proceed safely.

## Frequently Asked Questions

### How does Desktop Commander prevent configuration file corruption during concurrent tool calls?

The `ConfigManager` maintains a private `writeChain` Promise that serializes every disk write operation. By appending each `fs.writeFile` call to the chain using `.then()`, the system guarantees that writes execute sequentially rather than concurrently, eliminating race conditions that could interleave bytes or result in partial JSON structures.

### What is the difference between `saveConfig()` and `scheduleSave()`?

`saveConfig()` creates an immediate, blocking write operation that callers can await to confirm the configuration is persisted to disk, suitable for critical updates. `scheduleSave()` implements write coalescing for high-frequency changes—it returns immediately and collapses rapid successive calls into a single background write, preventing thread-pool saturation while maintaining eventual consistency.

### Where does Desktop Commander store its configuration file?

The application stores settings in [`config.json`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/config.json) located in the user's home directory, with the path defined in [`src/config.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config.ts) as `CONFIG_FILE`. The `ConfigManager` class in [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) handles all file I/O operations, ensuring the JSON structure is atomically rewritten on every save.

### How does the writeChain handle write errors without breaking future operations?

Each link in the writeChain includes error isolation via `.catch(() => {})`. When a specific write operation fails, the catch handler prevents the rejection from propagating up the chain, allowing `this.writeChain` to reset to a resolved state. This ensures that a single disk error does not permanently block subsequent configuration updates.