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

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) (lines 33-52):

// 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).

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).

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)), 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:

// 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 – Core logic for acquiring/releasing the lock and diagnostic record handling
  2. internal/daemon/lock_unix.go – Unix-specific flock implementation
  3. internal/daemon/lock_windows.go – Windows-specific LockFileEx implementation
  4. 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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →