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_HOMEpath. - 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_HOMEcontrols the root directory for all no-mistakes daemon data, defaulting to~/.no-mistakes.- The resolution logic in
internal/paths/paths.gochecksos.Getenv("NM_HOME")and falls back to the home directory if unset. - The daemon explicitly reads and preserves
NM_HOMEininternal/daemon/daemon.goto ensure consistent state across its lifecycle. - Use the
--rootflag incmd/no-mistakes/main.goas an alternative to setting the environment variable directly. - Custom
NM_HOMEvalues 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →