No-Mistakes Environment Variables: Complete Guide to NM_HOME, Logging, and Daemon Control

The no-mistakes CLI and daemon are controlled by environment variables including NM_HOME for state directory resolution, NM_DEMO for demo mode, and NM_TEST_* variables for test harness configuration, all read via os.Getenv or the upsertEnv helper in the Go source.

The kunchenguid/no-mistakes repository relies on a small, well-documented set of environment variables to locate per-user state and configure daemon behavior. These variables are read directly from the process environment using standard library calls like os.Getenv and custom helpers, providing fine-grained control over data persistence, service lifecycle, and network proxy configuration.

Core State and Runtime Variables

NM_HOME – Root Directory for State

The NM_HOME variable defines the root directory for all persistent files, including the database, daemon socket, configuration, and logs. If unset, the code in internal/paths/paths.go defaults to $HOME/.no-mistakes.

Path resolution flows through paths.New() → paths.WithRoot() → paths.Root(), ensuring all I/O operations are scoped consistently. When the daemon registers as a managed service (launchd, systemd, or Windows Task), the service name incorporates a hash of NM_HOME via serviceInstanceSuffix, preventing collisions between different state directories.

NM_DEMO – Demo Mode Configuration

Setting NM_DEMO=1 enables demo mode, where pipeline steps skip real network calls and return static data. This mode is implemented in internal/pipeline/steps/demo.go and is used primarily for recorded end-to-end demonstrations.

NM_DAEMON – Daemon Process Flag

The NM_DAEMON variable is set internally by the main binary when it re-executes itself as a background process (NM_DAEMON=1). As implemented in internal/daemon/selfexec.go, this flag signals the entry point to run the daemon loop rather than the CLI, allowing the same binary to serve both interactive and background roles.

Daemon Control and Test Harness Variables

Bypassing Service Management in Tests

NM_TEST_START_DAEMON acts as an escape hatch for the test harness. When set to 1, the function defaultServiceManagerBypassed() in internal/daemon/service.go pretends the managed service is already running, avoiding interactions with system-level service managers like systemd or launchd.

Daemon Startup Timing Controls

Two variables fine-tune the daemon startup polling behavior used during testing:

  • NM_TEST_DAEMON_START_TIMEOUT – Overrides the default 5-second startup timeout (parsed via durationFromEnv in internal/daemon/selfexec.go)
  • NM_TEST_DAEMON_START_POLL_INTERVAL – Controls how frequently the test harness polls the daemon for readiness

Helper Process and Internal Flags

NM_DAEMON_HELPER_PROCESS is an internal flag used by the daemon's self-exec logic to spawn "helper" child processes. Valid values include 1 or block, which are consumed in internal/daemon/helpers_test.go and internal/daemon/service_test.go to force early exit or block the helper for testing purposes.

Capturing NM_HOME for Verification

NM_CAPTURE_NM_HOME_FILE specifies a file path where the daemon writes the effective NM_HOME value it sees. This verification mechanism, used in internal/daemon/service_test.go, ensures that a daemon launched under a different NM_HOME does not clobber another instance's state.

Network and Logging Configuration

Proxy Environment Variables

The daemon automatically forwards standard proxy variables into its managed service environment so agents can reach the internet through corporate proxies. The relevant keys are collected in serviceProxyEnv within internal/daemon/service.go:

  • HTTP_PROXY / http_proxy
  • HTTPS_PROXY / https_proxy
  • NO_PROXY / no_proxy
  • ALL_PROXY / all_proxy

These values are written into the generated launchd, systemd, or Windows Task definition with mode 0600, preventing accidental credential exposure.

Test Logging Conventions

While the daemon itself writes to OS-managed logs (systemd journal, macOS Console, Windows Event Log), the test harness uses NO_MISTAKES_CALL_LOG (sometimes referenced as NM_LOG_FILE in documentation) to audit CLI invocations. As implemented in install_script_test.go, this variable specifies a path where the install script appends each CLI invocation, providing a simple audit trail for verification.

Shell Environment Plumbing

Two internal variables coordinate the shell-env sub-process that safely captures grand-child processes:

  • NM_SHELLENV_PIPE_HELPER – Set by the helper process
  • NM_SHELLENV_PIPE_READY – Points to a pipe file the parent monitors

These are defined in internal/shellenv/shell_command_unix_test.go and handle coordination between the CLI and its spawned shells.

How Variables Interact at Runtime

At startup, the CLI builds a paths.Paths object by checking NM_HOME and falling back to the default. When starting the daemon:

  1. The daemon command sets NM_HOME explicitly via os.Setenv("NM_HOME", root) before forking
  2. The child process launches with NM_DAEMON=1 to enter daemon mode
  3. If NM_TEST_START_DAEMON=1 is present, the service manager integration is bypassed
  4. Proxy variables are captured once and baked into the service definition via serviceProxyEnv

This ensures that state isolation, network configuration, and service management are deterministic and testable.

Practical Code Examples

Configure a custom state directory and run in demo mode:

export NM_HOME=$HOME/.no-mistakes-demo
export NM_DEMO=1
no-mistakes daemon start

Override daemon startup timeout during integration testing:

export NM_TEST_DAEMON_START_TIMEOUT=10s
export NM_TEST_DAEMON_START_POLL_INTERVAL=100ms
go test ./internal/daemon/...

Capture the effective NM_HOME for verification:

export NM_CAPTURE_NM_HOME_FILE=/tmp/captured_home
no-mistakes daemon start
cat /tmp/captured_home  # Outputs the path the daemon resolved

Launch with corporate proxy settings:

export HTTP_PROXY="http://proxy.example.com:3128"
export NO_PROXY="localhost,127.0.0.1"
no-mistakes daemon start  # Proxy env is embedded in service definition

Programmatically create a paths object in Go:

import "github.com/kunchenguid/no-mistakes/internal/paths"

// Reads NM_HOME from environment automatically
p := paths.New("my/custom/home")
if err := daemon.Start(p); err != nil {
    log.Fatalf("daemon start failed: %v", err)
}

Summary

  • NM_HOME controls the root directory for all persistent state, defaulting to $HOME/.no-mistakes and processed through internal/paths/paths.go
  • NM_DEMO enables static demo mode by skipping network calls in internal/pipeline/steps/demo.go
  • NM_DAEMON signals the binary to run as a background service via internal/daemon/selfexec.go
  • NM_TEST_START_DAEMON and NM_TEST_DAEMON_START_TIMEOUT provide test harness control over service lifecycle and timing
  • Proxy variables (HTTP_PROXY, HTTPS_PROXY, etc.) are forwarded into managed service definitions in internal/daemon/service.go
  • NO_MISTAKES_CALL_LOG supports CLI audit logging for test verification

Frequently Asked Questions

What happens if NM_HOME is not set?

If NM_HOME is empty, the code in internal/paths/paths.go automatically resolves the path to $HOME/.no-mistakes. All subsequent database, socket, and log operations are scoped under this default directory.

How does no-mistakes handle corporate proxy settings?

The daemon reads HTTP_PROXY, HTTPS_PROXY, NO_PROXY, and ALL_PROXY (including lowercase variants) from the environment via serviceProxyEnv in internal/daemon/service.go. These values are written into the generated systemd, launchd, or Windows Task configuration with restricted permissions (mode 0600), ensuring the managed service routes traffic correctly without exposing credentials.

Can I run the daemon without installing it as a system service?

Yes. Setting NM_TEST_START_DAEMON=1 bypasses the managed service logic entirely. As implemented in defaultServiceManagerBypassed() within internal/daemon/service.go, this flag causes the startup code to treat the daemon as already running, preventing interactions with system-level service managers while still executing the daemon loop.

What is the difference between NM_DAEMON and NM_TEST_START_DAEMON?

NM_DAEMON=1 is set by the binary itself when re-executing to become a background process, triggering the daemon entry point in internal/daemon/selfexec.go. In contrast, NM_TEST_START_DAEMON=1 is a test harness flag that prevents the service manager from attempting to register or start the daemon via system facilities, allowing tests to manage the process lifecycle directly.

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 →