How no-mistakes Merges Global and Repository Configuration

no-mistakes combines global defaults from $HOME/.config/no-mistakes/config.yaml with repository-specific .no-mistakes.yaml files using a conservative merge strategy where repository values override global settings except for security-sensitive fields like allow_repo_commands.

The open-source tool no-mistakes (available at kunchenguid/no-mistakes) handles complex configuration through a layered architecture. Understanding exactly how no-mistakes merges configuration ensures you can define organization-wide defaults while maintaining granular project control without accidental security degradation.

The Two-Stage Configuration Architecture

no-mistakes operates on a strict two-layer system that loads configuration sequentially before merging them into a single effective runtime state.

Global Configuration Layer

The global configuration resides in a user-wide file, defaulting to $HOME/.config/no-mistakes/config.yaml. This file establishes baseline behaviors, default agents, and organization-wide policies that apply across all repositories. When this file is missing, no-mistakes falls back to compiled-in defaults through the config.LoadGlobal function.

Repository Configuration Layer

Each Git repository may contain a .no-mistakes.yaml file at its root. This layer is parsed by config.LoadRepo and allows per-project overrides for settings like agent selection, auto-fix behaviors, and timeout values. Repository configurations are optional—if absent, the global configuration applies unmodified.

The Merge Process in internal/config/config.go

The actual merging occurs in internal/config/config.go, specifically through the Merge function and its helper methods. The process follows a deliberate sequence to ensure predictable outcomes.

Step 1: Loading Both Configurations

First, config.LoadGlobal reads the global YAML file, while config.LoadRepo parses the repository-specific file. Both functions return structured Config objects that represent their respective configuration layers.

Step 2: Executing the Merge

The Merge(global, repo) function creates a fresh Config struct and copies values from the global configuration. It then applies repository-specific overrides using a conservative strategy: repo values override global ones unless the repo field is nil or zero-valued.

This means numeric zeros, empty strings, and explicit nil values in the repository configuration do not erase global defaults. This prevents accidental deletions of important security or behavior settings.

Step 3: Applying Specialized Overrides

For nested structures, no-mistakes uses specific helper functions:

  • applyAutoFixOverrides: Merges auto-fix settings (like lint and ci thresholds), allowing the repository to override specific sub-fields while inheriting others from global.
  • applyIntentOverrides: Combines intent configuration, respecting repository toggles for enabled status and reader modifications.
  • applyTestOverrides: Handles test behavior settings such as evidence collection flags.

Precedence Rules and Override Behavior

The merge logic follows specific precedence rules depending on configuration categories:

Configuration Field Global Role Repository Role Final Result
Agent Used only if repo.Agent is empty Overrides global if non-empty Repo value if set; otherwise global inheritance
AutoFix Provides default thresholds Any non-nil sub-field overrides global counterpart Per-repo overrides for specified fields only
Intent Defines global enabled status and disabled_readers Can toggle enabled or modify readers Repo overrides with global fallback
Timeout/LogLevel Baseline values Repository supplies replacement values Repository values replace global
AllowRepoCommands Never read from global Security-critical per-repo flag only Repository value exclusively

Special Security Constraints

The merge process includes critical safeguards for security-sensitive configurations.

Repository-Only Commands

The allow_repo_commands field is strictly per-repository and never loaded from the global configuration file. As verified in internal/config/config_merge_test.go (specifically TestLoadGlobal_RejectsAllowRepoCommands), this prevents a malicious global configuration from enabling arbitrary command execution across all repositories. When allow_repo_commands is false (the default), the repository may only execute trusted default-branch configurations.

Agent Inheritance Pattern

The Agent field uses intentional empty-value inheritance. When a repository does not specify an agent, the merged result leaves Agent empty, causing the runtime to fall back to the global agent value. This differs from explicit overrides, where setting Agent in the repository configuration completely replaces the global selection.

Integration Points: CLI and Daemon

The merge logic serves two primary execution paths in the codebase:

CLI Wizard (internal/cli/wizard.go): Loads both configuration layers, calls config.Merge, and presents the effective configuration to users during interactive setup, allowing verification before execution.

Daemon Manager (internal/daemon/manager.go): Performs the same merge operation during daemon startup, ensuring that agent-driven actions use the properly merged view of global defaults and repository customizations.

Code Example: Merge Behavior in Practice

The test suite in internal/config/config_merge_test.go documents the precedence behavior:

global := &GlobalConfig{
    Agent: types.AgentClaude,
    AutoFix: config.AutoFixRaw{Lint: ptr(5)},
}
repo := &RepoConfig{
    Agent: types.AgentClaude,            
    AutoFix: config.AutoFixRaw{CI: ptr(0)}, 
}
cfg := Merge(global, repo)

After merging:

  • cfg.Agent equals AgentClaude (repository override)
  • cfg.AutoFix.Lint equals 5 (inherited from global)
  • cfg.AutoFix.CI equals 0 (repository override)

Summary

  • no-mistakes loads global configuration first, then overlays repository-specific settings from .no-mistakes.yaml.
  • Repository values override global defaults unless the repository field is nil or zero-valued, preventing accidental configuration deletion.
  • Security-sensitive fields like allow_repo_commands are repository-only and never merged from global files.
  • The merge occurs in internal/config/config.go via the Merge function, with specialized helpers for nested structures like AutoFix and Intent.
  • Both CLI and daemon execute the same merge logic to ensure consistent runtime behavior.

Frequently Asked Questions

Can a repository completely disable a global auto-fix setting?

A repository can override specific auto-fix sub-fields by providing explicit zero values (like ci: 0), but it cannot "delete" or unset a global configuration. The merge preserves global values for any fields not explicitly specified in the repository configuration, maintaining the conservative override principle established in internal/config/config.go.

Why is allow_repo_commands blocked from global configuration?

This restriction prevents privilege escalation where a compromised global configuration file could enable arbitrary command execution across all repositories. By forcing allow_repo_commands to be defined only in the per-repository .no-mistakes.yaml file (and validating this in config_merge_test.go), no-mistakes ensures that repository maintainers explicitly opt-in to command execution capabilities.

How does the daemon know which global configuration to use when running in CI?

The daemon calls config.LoadGlobal which defaults to $HOME/.config/no-mistakes/config.yaml, but this path can be influenced by environment variables or explicit flags. After loading, it merges with the repository configuration found in the current working directory or specified path, following the same precedence rules used in the CLI wizard.

What happens if both global and repository configurations define different agents?

The repository configuration wins. According to the merge logic in internal/config/config.go, if repo.Agent is non-empty, it replaces the global agent selection entirely. If the repository leaves Agent empty (nil or empty string), the runtime falls back to the global agent, creating an inheritance pattern rather than a hard override.

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 →