How to Configure the No-Mistakes Daemon’s Home Directory Using NM_HOME

Set the NM_HOME environment variable to override the default ~/.no-mistakes directory, or use the --root CLI flag which automatically sets NM_HOME for the current command.

The no-mistakes daemon stores all persistent data—including SQLite state, Unix sockets, logs, configuration files, and work-trees—under a single root directory. By default, this location is ~/.no-mistakes, but you can relocate it entirely by configuring the NM_HOME environment variable. This guide explains how the kunchenguid/no-mistakes repository implements this mechanism and how to use it for isolated testing, multiple daemon instances, or alternative storage locations.

Understanding NM_HOME and the Default Path

When the no-mistakes daemon starts, it determines where to store its artifacts by checking the NM_HOME environment variable. If NM_HOME is unset or empty, the daemon falls back to the user's home directory and appends .no-mistakes.

This root directory acts as the single source of truth for all sub-paths. Components throughout the codebase—from configuration loaders to the SQLite database handler—resolve their locations relative to this root using helpers defined in internal/paths/paths.go.

How the Daemon Resolves NM_HOME

The path resolution logic lives in internal/paths/paths.go, specifically within the New() function. This function reads the environment variable and returns a Paths struct containing the resolved root directory.

// internal/paths/paths.go
func New() (*Paths, error) {
    if env := os.Getenv("NM_HOME"); env != "" {      // ← reads NM_HOME
        return &Paths{root: env}, nil
    }
    home, err := os.UserHomeDir()
    if err != nil {
        return nil, err
    }
    return &Paths{root: filepath.Join(home, ".no-mistakes")}, nil
}

All higher-level components obtain their file paths from a *paths.Paths instance created by paths.New(). Because the root is derived from NM_HOME, changing this single environment variable automatically relocates every daemon artifact, including state.sqlite, the Unix socket, config.yaml, and the logs/ directory.

Daemon Initialization and Environment Handling

The daemon itself explicitly reads NM_HOME early in its execution to ensure consistency across its internal services. In internal/daemon/daemon.go, the daemon captures the environment variable at line 61 and later restores it upon exit to maintain process integrity.

// internal/daemon/daemon.go
nmHome := os.Getenv("NM_HOME")                         // ← env read
...
if err := os.Setenv("NM_HOME", nmHome); err != nil {   // restore after daemon exit
    return fmt.Errorf("restore NM_HOME: %w", err)
}

This ensures that even if the daemon spawns child processes or modifies its environment, the original NM_HOME value remains intact for cleanup operations.

CLI Flag Alternative: --root

For users who prefer command-line flags over environment variables, the top-level CLI wrapper in cmd/no-mistakes/main.go accepts a --root flag. When provided, the CLI writes the flag value directly into NM_HOME before executing any sub-command, achieving the same effect as setting the environment variable manually.

// cmd/no-mistakes/main.go (excerpt)
if err := os.Setenv("NM_HOME", root); err != nil {
    return fmt.Errorf("set NM_HOME: %w", err)
}

You can choose whichever style fits your workflow—explicit environment export for persistent sessions, or --root for one-off commands.

Practical Use Cases for Custom NM_HOME

Configuring a custom home directory is essential for several advanced workflows:

  • Isolated Testing: Point the daemon at a temporary directory ($TMPDIR/no-mistakes-test) to ensure test runs never touch production state or the real user profile.
  • Multiple Installations: Run separate daemon instances (e.g., stable release vs. development build) without name collisions in system service managers. The service names are automatically scoped using a short hash derived from the chosen NM_HOME path.
  • Alternative Storage Locations: Move the daemon’s state to a fast SSD, network share, or encrypted volume to meet performance or security requirements.

Implementation Examples

Setting NM_HOME in a Shell Session


# Choose a custom location for the daemon’s state

export NM_HOME=$HOME/.no-mistakes-dev

# Start the daemon (it will pick up NM_HOME automatically)

no-mistakes daemon start

# Verify the daemon is using the custom directory

ls "$NM_HOME"

# → config.yaml  state.sqlite  socket  logs/  worktrees/ …

Using the --root Flag


# One-off command that runs the daemon with a custom root

no-mistakes --root $HOME/.no-mistakes-dev daemon start

# The daemon will write its files under the supplied path.

Isolating NM_HOME in Go Tests

func TestMyFeature(t *testing.T) {
    tmp := t.TempDir()
    t.Setenv("NM_HOME", tmp)          // isolates all daemon state
    // … invoke CLI / daemon …
    // Verify that only the temporary directory was touched
    if _, err := os.Stat(filepath.Join(tmp, "state.sqlite")); err != nil {
        t.Fatalf("expected SQLite DB in %s", tmp)
    }
}

Verifying the Effective Home at Runtime

The axi status command displays the current daemon socket path, which reveals the effective NM_HOME prefix:

$ NM_HOME=$HOME/.no-mistakes-dev no-mistakes axi status
daemon socket: /home/user/.no-mistakes-dev/socket

Summary

  • NM_HOME controls the root directory for all no-mistakes daemon data, defaulting to ~/.no-mistakes.
  • The resolution logic in internal/paths/paths.go checks os.Getenv("NM_HOME") and falls back to the home directory if unset.
  • The daemon explicitly reads and preserves NM_HOME in internal/daemon/daemon.go to ensure consistent state across its lifecycle.
  • Use the --root flag in cmd/no-mistakes/main.go as an alternative to setting the environment variable directly.
  • Custom NM_HOME values enable isolated testing, parallel daemon instances, and flexible storage configurations.

Frequently Asked Questions

What happens if NM_HOME is not set?

If NM_HOME is undefined or empty, the daemon automatically uses ~/.no-mistakes as the root directory. This fallback is handled in internal/paths/paths.go by calling os.UserHomeDir() and joining it with the default directory name.

Can I use NM_HOME to run multiple daemon instances simultaneously?

Yes. Set distinct NM_HOME values for each instance (e.g., ~/.no-mistakes-stable and ~/.no-mistakes-dev). The daemon scopes its service names using a hash of the path, preventing conflicts in launchd, systemd, or Windows Task Scheduler while keeping all state completely isolated.

Does the --root flag override an existing NM_HOME environment variable?

Yes. When you use --root, the CLI explicitly calls os.Setenv("NM_HOME", root) before executing the sub-command, overwriting any existing value for the duration of that process. This ensures the flag takes precedence over the environment.

Where is the NM_HOME path actually used in the codebase?

The path is resolved in internal/paths/paths.go (function New()), read by the daemon in internal/daemon/daemon.go at line 61, and optionally set via the --root flag handler in cmd/no-mistakes/main.go. All file operations throughout the codebase reference the *Paths instance derived from this root.

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 →