# How the Daemon Singleton Pattern Prevents Multiple Instances in no-mistakes

> Learn how the daemon singleton pattern in no-mistakes uses OS file locks to prevent multiple daemon instances, ensuring a single active process and data integrity.

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

---

**The no-mistakes daemon implements a singleton guard using an exclusive OS-level file lock on `<NM_HOME>/daemon.lock`, acquired before any initialization work and held until process termination to ensure only one live daemon owns a given home directory.**

The kunchenguid/no-mistakes repository protects against concurrent daemon corruption through a robust **daemon singleton pattern** that leverages kernel-enforced file locking. This mechanism guarantees that only one instance can execute destructive startup operations like stale-run recovery or worktree cleanup within a specific `NM_HOME` directory, rejecting any subsequent start attempts with diagnostic error information.

## OS-Level File Locking in internal/daemon/lock.go

The core protection lives in [`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go), specifically within the `acquireSingletonLock` function. This method attempts to create and exclusively lock a file at `<NM_HOME>/daemon.lock` using platform-specific helpers (`tryLockFile` and `unlockFile`).

The lock is **exclusive and non-blocking**. If another process already holds the lock, the kernel immediately returns a busy status, causing `acquireSingletonLock` to return `ErrSingletonLockHeld`. Because this is an OS-level advisory lock, the kernel automatically releases it if the owning process exits, crashes, or receives a SIGKILL—eliminating the need for heartbeat checks or PID files.

```go
// Acquire the singleton lock – fails if another daemon is already running.
lock, err := acquireSingletonLock(p)
if err != nil {
    // ErrSingletonLockHeld is returned when another daemon holds the lock.
    return err
}
defer lock.Release() // Guarantees the lock is released on shutdown.

```

## Guard Placement and Early Acquisition

The daemon enforces the singleton pattern at the earliest possible moment. In [`internal/daemon/daemon.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/daemon.go), the `RunWithOptions` function calls `acquireSingletonLock` immediately upon startup (around lines 19–28), before any destructive work like orphan worktree cleanup or IPC socket binding occurs.

This placement ensures that a racing second instance aborts cleanly before it can corrupt shared state. The code defers `lock.Release()` right after acquisition, leveraging Go's defer stack to guarantee cleanup even if subsequent initialization fails.

```go
// Inside RunWithOptions – the lock is taken before any other initialization.
func RunWithOptions(p *paths.Paths, d *db.DB, stepFactory StepFactory) error {
    // Singleton guard: only one live daemon may own this NM_HOME at a time.
    lock, err := acquireSingletonLock(p)
    if err != nil {
        return err
    }
    defer lock.Release()
    // ... rest of daemon startup …
}

```

## Diagnostic Records and Error Handling

When the lock is successfully acquired, the daemon writes a human-readable JSON diagnostic record to the lock file. This record, defined as `lockHolderRecord`, contains the process PID and UTC start time (lines 53–62 of [`lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/lock.go)). While the actual safety mechanism relies on the OS lock, this metadata helps operators identify which process currently holds the lock.

If acquisition fails, the returned error wraps the holder's PID and start time, allowing client code to surface precise information:

```go
// lock.go – writes a diagnostic JSON record after the lock is taken.
record := lockHolderRecord{PID: os.Getpid(), StartedAt: time.Now().UTC()}
if data, err := json.Marshal(record); err == nil {
    _ = f.Truncate(0)
    _, _ = f.WriteAt(data, 0)
    _ = f.Sync()
}

```

Callers in [`daemon.go`](https://github.com/kunchenguid/no-mistakes/blob/main/daemon.go) (lines 28–32) handle this by returning the error directly, preventing the second daemon from proceeding and informing the user exactly which instance is blocking startup.

## Automatic Lock Release and Cleanup Safety

The `Release` method in [`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go) unlocks the file descriptor and closes the underlying file. It is implemented to be **nil-safe**—calling `Release()` on a nil lock pointer returns without error. This design pattern allows the code to defer `lock.Release()` immediately after the acquisition attempt, even when `lock` might be nil due to an error.

The test suite in [`internal/daemon/lock_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_test.go) (lines 74–80) validates this nil-safety behavior, ensuring that cleanup logic never panics. Combined with the kernel's automatic lock release on process termination, this guarantees that the singleton protection is both robust against crashes and safe to maintain.

## Summary

- **OS-level exclusive lock**: The `acquireSingletonLock` function in [`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go) creates an exclusive, non-blocking lock on `<NM_HOME>/daemon.lock` using kernel file locking primitives.
- **Early enforcement**: `RunWithOptions` in [`internal/daemon/daemon.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/daemon.go) acquires the lock before any destructive startup work, preventing race conditions in run metadata or worktree management.
- **Diagnostic clarity**: Failed acquisitions return `ErrSingletonLockHeld` wrapped with the blocking process's PID and start time, enabling precise troubleshooting.
- **Crash-safe release**: The kernel automatically releases the lock when the process exits or is killed, requiring no separate cleanup daemon or heartbeat mechanism.
- **Defensive programming**: The `Release` method is nil-safe and deferred immediately after acquisition, preventing resource leaks even when startup fails.

## Frequently Asked Questions

### What happens to the lock if the no-mistakes daemon crashes unexpectedly?

The kernel automatically releases the OS-level file lock when the owning process terminates, regardless of whether it exits cleanly, crashes, or is killed via SIGKILL. This happens without requiring any cleanup code to run, ensuring that a new daemon can start immediately after the previous one disappears, as implemented in [`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go).

### How can I identify which process is blocking a new daemon startup?

When `acquireSingletonLock` detects an existing lock, it returns `ErrSingletonLockHeld` wrapped with a JSON diagnostic record containing the holder's PID and start time. This information is written to the lock file at `<NM_HOME>/daemon.lock` and surfaced in the error returned by `RunWithOptions` in [`internal/daemon/daemon.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/daemon.go).

### Where is the singleton lock file stored?

The lock file is created at `<NM_HOME>/daemon.lock` within the configured no-mistakes home directory. The path is derived from the `*paths.Paths` configuration passed to `acquireSingletonLock` in [`internal/daemon/lock.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock.go).

### Is it safe to call the lock release method if acquisition fails?

Yes. The `Release` method is explicitly designed to be safe when called on a nil receiver, as validated in [`internal/daemon/lock_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/lock_test.go) (lines 74–80). This allows the code to defer `lock.Release()` immediately after the acquisition attempt without checking for nil, preventing resource leaks in error paths.