# How nodeterm Ensures Atomic Writes to Its Stores: A Deep Dive into the Filesystem Layer

> Discover how nodeterm ensures atomic writes to its stores using temporary files and atomic renames. Learn about Windows compatibility and cleanup safeguards.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: deep-dive
- Published: 2026-08-26

---

**nodeterm guarantees data integrity by writing to unique temporary files and atomically renaming them into place, with automatic retry logic for Windows compatibility and comprehensive cleanup safeguards.**

nodeterm is a terminal emulator that requires rock-solid persistence for user data ranging from workspace layouts to credential files. To prevent data corruption during crashes or concurrent access, the project implements a robust **atomic write** strategy centralized in [`src/core/fs-atomic.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/fs-atomic.ts), ensuring readers always see either the previous complete state or the new complete state—never a partial write.

## The Three-Stage Atomic Commit Process

### Stage 1: Isolated Temporary File Creation

Every write operation begins by generating a unique temporary filename via `tempNameFor`. This function encodes the target path, current process PID, a sequence counter, and a UUID into the filename (e.g., `settings.json.12345.1.550e8400-e29b-41d4-a716-446655440000.tmp`). This naming convention eliminates collision risks when multiple processes write simultaneously.

### Stage 2: Atomic Publication with Cross-Platform Retries

After writing data to the temporary file with exclusive mode flags (`wx`), the system attempts to publish the content using `renameAtomic`. On POSIX systems, `fs.rename` is atomic by design. However, Windows can throw transient sharing-violation errors (`EPERM`, `EACCES`, `EBUSY`). The `renameAtomic` implementation automatically retries the operation with exponential backoff delays of 10 ms, 25 ms, 75 ms, and 200 ms before failing definitively.

### Stage 3: Defensive Cleanup on Failure

If any step in the write pipeline fails, `writeFileAtomic` immediately removes the temporary file using `fs.rm(..., { force: true })`. This ensures incomplete writes never persist, leaving the original target file untouched and valid.

## Handling Deletions and Maintenance

### Atomic Removal with `removeAtomic`

The companion function `removeAtomic` applies the same retry logic to file deletions, treating `ENOENT` (file not found) as success. This prevents race conditions where a file is deleted between existence checks and removal attempts.

### The Stale Temp File Sweeper

To prevent disk pollution from abandoned temporary files (e.g., after process crashes), nodeterm runs `sweepStaleTempFiles`, which removes temporary files older than 24 hours. This maintenance routine keeps data directories clean without risking active writes.

## Enforcing Atomicity Across the Codebase

### The Guard Test Pattern

A specialized test file, [`src/core/fs-atomic.guard.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/fs-atomic.guard.test.ts), scans the entire codebase to verify that all stores use `writeFileAtomic` or `renameAtomic` rather than raw `fs.rename` calls. This automated check prevents regression and ensures consistent atomicity guarantees across settings, workspace layouts, credential files, and scrollback stores.

### Integration in Core Stores

The implementation enforces usage across critical components. For example, [`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts) persists layout data via `writeFileAtomic`, while [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts) uses the same pathway for terminal history snapshots. SSH project management in [`src/main/remote-ssh/ssh-project.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/remote-ssh/ssh-project.ts) combines `removeAtomic` and `renameAtomic` for safe file operations.

## Practical Implementation Examples

Use `writeFileAtomic` for standard JSON persistence:

```ts
import { writeFileAtomic } from '@/core/fs-atomic'

// `targetPath` is the final location of the file
await writeFileAtomic(targetPath, JSON.stringify(workspaceData, null, 2))

```

For scenarios where you have already prepared a temporary file, use explicit atomic renaming:

```ts
import { renameAtomic } from '@/core/fs-atomic'

await renameAtomic(tempPath, targetPath)

```

Handle deletions safely with Windows-compatible retry logic:

```ts
import { removeAtomic } from '@/core/fs-atomic'

const deleted = await removeAtomic(targetPath)
if (!deleted) {
  // handle the case where the file could not be removed
}

```

Generate unique temporary names for custom workflows:

```ts
import { tempNameFor } from '@/core/fs-atomic'

const tmp = tempNameFor(targetPath)  // e.g. "/data/settings.json.12345.1.550e8400-e29b-41d4-a716-446655440000.tmp"

```

## Summary

- **Unique temporary files** prevent write collisions by encoding PID, sequence counters, and UUIDs into filenames via `tempNameFor`.
- **Atomic publication** uses `renameAtomic` with platform-specific retry logic (handling `EPERM`, `EACCES`, `EBUSY` on Windows) to ensure readers never see partial data.
- **Automatic cleanup** removes temporary files on write failure and sweeps stale temps older than 24 hours via `sweepStaleTempFiles`.
- **Enforcement testing** via [`src/core/fs-atomic.guard.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/fs-atomic.guard.test.ts) ensures all stores use atomic helpers instead of raw filesystem operations.

## Frequently Asked Questions

### What happens if nodeterm crashes during an atomic write?

If the process terminates after creating the temporary file but before the rename completes, the original target file remains intact and readable. The orphaned temporary file will be cleaned up by `sweepStaleTempFiles` after 24 hours. Readers never encounter corrupted or incomplete data because the new content is only visible after the atomic rename succeeds.

### Does this atomic write strategy work reliably on Windows?

Yes. While POSIX systems rely on native atomic `fs.rename` semantics, the `renameAtomic` implementation specifically handles Windows-specific sharing violations (`EPERM`, `EACCES`, `EBUSY`) through automatic retries with exponential backoff delays (10 ms, 25 ms, 75 ms, 200 ms). This ensures cross-platform consistency without platform-specific code in the stores themselves.

### How does nodeterm prevent temporary file accumulation?

The `sweepStaleTempFiles` utility automatically deletes temporary files older than 24 hours. Additionally, the `writeFileAtomic` function immediately cleans up temporary files using `fs.rm(..., { force: true })` if any step fails, preventing accumulation from interrupted writes.

### Which nodeterm components use atomic writes?

All persistent stores in the application are required to use these helpers, including workspace layouts ([`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts)), terminal scrollback history ([`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts)), GitHub control caches ([`src/main/github-control.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/github-control.ts)), and SSH project files ([`src/main/remote-ssh/ssh-project.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/remote-ssh/ssh-project.ts)). The guard test ensures no store bypasses these atomic primitives.