# How the Daemon Singleton Lock Prevents Multiple Instances in No-Mistakes

> Discover how the daemon singleton lock in no-mistakes uses OS-level file locking to ensure only one instance runs per NM_HOME, automatically releasing the lock on termination.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-13

---

**The no-mistakes daemon guarantees that only one live daemon can run per `NM_HOME` by acquiring an OS-level exclusive file lock at startup, automatically releasing it when the process terminates.**

The singleton pattern is critical for the no-mistakes daemon to safely manage work-trees and IPC sockets without race conditions. By leveraging kernel-managed file locking rather than PID files, the implementation eliminates stale lock scenarios and provides immediate failure detection when a second instance attempts to start.

## OS-Level File Locking Architecture

The singleton mechanism relies on platform-specific syscalls to obtain mandatory exclusive locks on a dedicated lock file, ensuring cross-process synchronization at the operating system level.

### Lock Acquisition Flow

The entry point `acquireSingletonLock` orchestrates the singleton check by opening the lock file obtained from `p.LockFile()` and attempting to acquire the exclusive lock through `tryLockFile`. If another process already holds the lock, the function immediately returns `ErrSingletonLockHeld` along with diagnostic information identifying the current holder.

This logic is implemented in [[`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go)](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go) (lines 33-52):

```go
// Example: acquiring the singleton lock when starting the daemon
func startDaemon(p *paths.Paths) error {
    lock, err := acquireSingletonLock(p)
    if err != nil {
        // Another daemon is already running – report and exit
        return fmt.Errorf("cannot start daemon: %w", err)
    }
    // Ensure the lock is released on exit
    defer lock.Release()

    // … continue with daemon initialization …
    return runDaemon()
}

```

### Unix Implementation

On Linux and macOS, the daemon utilizes `syscall.Flock` with `LOCK_EX|LOCK_NB` flags to obtain a **non-blocking exclusive advisory lock**. This syscall attaches the lock directly to the open file descriptor, and the kernel automatically releases it when the process exits or crashes, even if the crash is unclean.

The platform-specific logic resides in [[`internal/daemon/lock_unix.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_unix.go)](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_unix.go).

### Windows Implementation

On Windows, the implementation uses `LockFileEx` on a far-offset byte range (`0xFFFFFFFF`) to create a mandatory lock. Like the Unix version, this lock is process-associated and released automatically when the owning process terminates, regardless of exit conditions.

The Windows-specific implementation is found in [[`internal/daemon/lock_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_windows.go)](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_windows.go).

## Self-Cleaning Safety Properties

Because the lock is managed by the OS kernel rather than application-level logic, a stale lock cannot survive a crashed daemon. This eliminates the classic "PID file orphaned by crash" problem that requires manual cleanup scripts.

The lock file does store a small JSON diagnostic record containing `PID` and `StartedAt` timestamps (lines 53-62 of [[`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go)](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go)), but this serves only for observability and troubleshooting. The safety guarantee relies solely on the OS-level lock semantics.

## Startup Integration

The singleton lock is obtained **before** any destructive startup work—including stale-run recovery and work-tree cleanup—and before the IPC socket is bound. This sequencing ensures that no two daemons can race to perform initialization actions that could corrupt the shared state.

When handling startup errors, you can specifically detect the singleton conflict:

```go
// Handling the specific ErrSingletonLockHeld error
if errors.Is(err, daemon.ErrSingletonLockHeld) {
    fmt.Println("A daemon is already running for this NM_HOME.")
    // Optionally log the holder information from the error message
}

```

## Implementation Details by Platform

The cross-platform abstraction delegates to OS-specific functions while maintaining a consistent interface:

1. **[`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go)** – Core logic for acquiring/releasing the lock and diagnostic record handling
2. **[`internal/daemon/lock_unix.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_unix.go)** – Unix-specific `flock` implementation
3. **[`internal/daemon/lock_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_windows.go)** – Windows-specific `LockFileEx` implementation
4. **[`internal/paths/paths.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/paths/paths.go)** – Provides `LockFile()` returning the full path to `daemon.lock` under `NM_HOME`

## Summary

- **Kernel-managed reliability**: The daemon singleton lock uses OS-level file locks (`flock` on Unix, `LockFileEx` on Windows) that automatically release when the process terminates.
- **Immediate conflict detection**: `acquireSingletonLock` in [`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go) fails immediately with `ErrSingletonLockHeld` if another instance is running.
- **Crash-safe architecture**: Unlike PID-file approaches, the lock cannot outlive the process, preventing stale lock scenarios.
- **Diagnostic visibility**: The lock file stores JSON metadata (PID, start time) for troubleshooting while relying on OS primitives for actual exclusion.

## Frequently Asked Questions

### What happens if the daemon crashes while holding the singleton lock?

The OS kernel automatically releases the file lock when the process exits or crashes, regardless of whether the shutdown is clean. This self-cleaning property means you never need to manually remove stale locks after a crash, unlike traditional PID-file singleton implementations.

### How does the lock work across different operating systems?

The implementation uses conditional compilation to select platform-specific syscalls. On Unix systems (Linux/macOS), it calls `syscall.Flock` with exclusive non-blocking flags. On Windows, it calls `LockFileEx` on a specific byte range. Both implementations provide the same guarantee: only one process can hold the lock at any time.

### Where is the lock file stored?

The lock file path is determined by `p.LockFile()` in the paths package, which returns `daemon.lock` located under the `NM_HOME` directory. This ensures that multiple users on the same machine can run separate daemon instances provided they use different `NM_HOME` directories.

### Can I manually release the lock if needed?

You should not manually release the lock as it is tightly bound to the process lifecycle through the file descriptor. If you need to force a new daemon instance to start, you must terminate the existing process holding the lock. The `acquireSingletonLock` function returns detailed error information including the holder's PID to help identify which process to stop.