How the Daemon Singleton Lock Prevents Concurrent Daemon Instances in no-mistakes

The no-mistakes daemon uses an OS-level exclusive file lock to guarantee that only one instance runs per NM_HOME, automatically releasing the lock when the process exits or crashes.

The kunchenguid/no-mistakes repository implements a robust singleton pattern for its daemon process using kernel-managed file locking. This mechanism ensures that destructive startup operations—such as stale-run recovery and work-tree cleanup—never execute concurrently across multiple daemon instances. The implementation spans multiple platform-specific files while maintaining a consistent interface through the acquireSingletonLock function.

Lock Acquisition and Detection

The singleton lock mechanism centers on the acquireSingletonLock function defined in internal/daemon/lock.go (lines 33-52). When starting, the daemon attempts to open the lock file via p.LockFile() and calls the platform-specific tryLockFile helper.

If another process already holds the lock, the function immediately returns ErrSingletonLockHeld along with diagnostic information identifying the holding process. This check occurs before any IPC socket binding or destructive startup work, eliminating race conditions during initialization.

Platform-Specific Implementations

Unix: Advisory Locks with flock

On Linux and macOS, the implementation in internal/daemon/lock_unix.go uses syscall.Flock with the LOCK_EX|LOCK_NB flags. This creates a non-blocking exclusive advisory lock on the file descriptor.

The kernel automatically associates the lock with the process, ensuring it releases immediately upon termination—even if the daemon crashes unexpectedly. This provides the self-cleaning property without requiring explicit lock file deletion.

Windows: Mandatory Locks with LockFileEx

The Windows implementation in internal/daemon/lock_windows.go utilizes LockFileEx on a far-offset byte range (0xFFFFFFFF) to create a mandatory lock. Like the Unix version, the Windows kernel automatically releases this lock when the owning process terminates.

Both implementations share the same guarantee: the lock cannot survive a crashed daemon because the OS kernel manages the resource rather than the application.

Diagnostic Records and Error Handling

While the safety guarantee relies solely on the OS lock, internal/daemon/lock.go (lines 53-62) stores a small JSON diagnostic record containing the PID and StartedAt timestamp. This metadata enables informative error messages when ErrSingletonLockHeld occurs, helping administrators identify the running daemon instance.

Integration in Daemon Startup

The singleton lock is acquired at the earliest possible stage in the daemon lifecycle. This ordering ensures that no two daemons can simultaneously attempt stale-run recovery or work-tree cleanup operations that might corrupt the NM_HOME directory.

Practical Implementation Examples

The following example demonstrates acquiring the lock during daemon initialization:

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()
}

When handling the specific singleton lock 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
}

Summary

  • Kernel-level exclusivity: Uses syscall.Flock on Unix and LockFileEx on Windows to obtain OS-managed exclusive locks.
  • Automatic cleanup: The lock releases automatically when the process exits or crashes, preventing stale lock files.
  • Early acquisition: The lock is obtained before destructive startup operations and IPC binding to prevent race conditions.
  • Diagnostic support: Lock files contain JSON metadata (PID, StartedAt) for identifying the holding process when ErrSingletonLockHeld occurs.
  • Cross-platform consistency: Unified interface in internal/daemon/lock.go with platform-specific implementations in lock_unix.go and lock_windows.go.

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 terminates, regardless of whether it exits cleanly or crashes. This self-cleaning property ensures that stale locks never survive daemon crashes, unlike application-level locking mechanisms that might leave lock files on disk.

Can the daemon singleton lock be bypassed or circumvented?

No, the lock relies on kernel-level primitives (flock on Unix, LockFileEx on Windows) that are enforced by the operating system. While the lock file contains diagnostic JSON data, the actual exclusivity guarantee comes from the OS-managed file descriptor lock, which cannot be bypassed by standard user-space operations.

Where does the daemon store its singleton lock file?

The lock file path is determined by the LockFile() method on the paths.Paths struct, typically located under the NM_HOME directory. The specific implementation resides in internal/daemon/lock.go, which coordinates with platform-specific helpers in internal/daemon/lock_unix.go and internal/daemon/lock_windows.go.

Why does the lock acquisition happen before IPC socket binding?

Acquiring the singleton lock before binding the IPC socket ensures that only one daemon instance can perform destructive startup operations like stale-run recovery and work-tree cleanup. This ordering prevents race conditions where multiple instances might simultaneously attempt to initialize the same NM_HOME resources.

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 →