How DesktopCommanderMCP's ConfigManager Serializes Writes to Prevent Corruption
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 simultaneously, race conditions could corrupt the file. The ConfigManager class in 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 (lines 55-58):
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:
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:
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:
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 (lines 86-88):
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.
// 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.
// 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
writeChainpromise 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 thesaveScheduledflag, reducing disk pressure while maintaining the serialization guarantee.writeConfigToDisk()performs atomic JSON serialization through a singlefs.writeFilecall, ensuring that the configuration file is never left in a partially-written state.- Callers choose between immediate consistency via
setValue()or deferred persistence viasetValueNonBlocking()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). 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) as CONFIG_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →