How DesktopCommanderMCP's Config Manager Write Chain Prevents Configuration Corruption
The writeChain in DesktopCommanderMCP's ConfigManager prevents corruption by serializing all disk writes through a single promise chain, ensuring no two writes run concurrently and blocking partial writes from reaching the filesystem.
The DesktopCommanderMCP repository implements a robust configuration persistence layer that handles concurrent updates, partial write failures, and high-frequency changes without corrupting config.json. The ConfigManager class in src/config-manager.ts achieves this through a carefully designed writeChain mechanism that orders every file operation sequentially.
Single Promise Chain Eliminates Race Conditions
The foundation of corruption prevention starts at initialization. In src/config-manager.ts at line 56, the constructor creates writeChain as a resolved promise:
this.writeChain = Promise.resolve();
Every subsequent write appends to this chain using .then(). Because JavaScript promises settle exactly once, each writeConfigToDisk call must complete before the next begins. Simultaneous invocations of setValue(), saveConfig(), or scheduleSave() cannot interleave their filesystem operations— they queue automatically through the chain.
Atomic File Writes via writeConfigToDisk
The actual persistence logic resides in writeConfigToDisk at line 197:
private async writeConfigToDisk(): Promise<void> {
await fs.writeFile(
this.configPath,
JSON.stringify(this.config, null, 2),
'utf-8'
);
}
This performs a single fs.writeFile call with the fully stringified configuration object. The operation is atomic at the system call level: the file either contains the complete new JSON blob or the previous valid state. No partially written data ever appears on disk.
Error-Resilient Chain Continuation
Failed writes cannot break the serialization guarantee. After scheduling each write, the manager updates writeChain and attaches an empty .catch() handler at line 208:
this.writeChain = this.writeChain.then(() => this.writeConfigToDisk()).catch(() => {});
A rejected promise would normally halt subsequent .then() handlers. The .catch(() => {}) swallows errors, allowing the chain to continue. Later writes proceed regardless of earlier failures, maintaining ordering guarantees even when disk space is exhausted or permissions are denied.
Coalesced Background Saves for High-Frequency Updates
Telemetry counters and similar non-critical updates use scheduleSave() to avoid disk thrashing. This method implements two protective mechanisms in src/config-manager.ts starting at line 220:
-
saveScheduledflag guard — If a background write is already queued, subsequent calls return immediately without creating new chain entries. -
Single merged write — When the delayed write executes at line 226, it resets
saveScheduledand callswriteConfigToDiskonce. All in-memory changes accumulated during the delay persist together.
This collapsing behavior prevents a rapid stream of updates from generating hundreds of individual file writes, reducing wear on SSDs and eliminating windows where crashes could leave partially updated state.
Explicit Blocking Saves for Critical Operations
User-initiated actions requiring guaranteed persistence call saveConfig() at line 205:
public async saveConfig(): Promise<void> {
await this.writeChain;
}
Like background saves, this appends to writeChain. The returned promise resolves only after all prior writes complete, ensuring that even if scheduleSave() queued a write milliseconds earlier, the explicit save waits its turn and observes the fully updated state.
Practical Usage Examples
import { configManager } from './config-manager.js';
// Critical UI action: blocks until written
await configManager.setValue('defaultShell', '/bin/bash');
// Telemetry update: coalesced in background
await configManager.setValueNonBlocking('commandCount', 42);
// Bulk update: only one disk write despite 100 calls
for (let i = 0; i < 100; i++) {
configManager.setValueNonBlocking(`key${i}`, i);
}
Summary
-
Serial execution: The
writeChainpromise queue guarantees one filesystem operation at a time, preventing interleaved writes that could corrupt JSON structure. -
Atomic writes:
writeConfigToDiskwrites complete stringified state in a single system call viafs.writeFile. -
Fault tolerance:
.catch(() => {})handlers ensure chain continuity through disk errors without dropping later updates. -
Write coalescing:
scheduleSave()collapses rapid updates into single operations via thesaveScheduledflag, reducing I/O load and crash vulnerability. -
Explicit durability:
saveConfig()provides blocking persistence for operations requiring immediate, confirmed storage.
Frequently Asked Questions
What happens if two setValue() calls run simultaneously?
Both calls append their writes to writeChain in the order received. The first queued writeConfigToDisk executes completely before the second begins. The later call reads the updated in-memory state, so its write includes all prior changes.
Does writeChain prevent data loss if the process crashes?
writeChain prevents file corruption, not data loss. Unsaved in-memory state disappears on crash. For durability, use await saveConfig() or setValue() (which saves) before critical operations. Background-scheduled saves may lose their delay window.
Why use .catch(() => {}) instead of proper error handling?
The empty catch keeps the chain alive for subsequent writes. Individual write failures are logged separately in writeConfigToDisk. Without this handler, a single disk error would permanently stall all future configuration updates.
How does scheduleSave() differ from direct saveConfig() calls?
scheduleSave() is fire-and-forget: it updates memory immediately but delays disk I/O, coalescing multiple calls. saveConfig() and setValue() wait for the chain to settle, returning only after the file reflects current state.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →