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 (likelintandcithresholds), allowing the repository to override specific sub-fields while inheriting others from global.applyIntentOverrides: Combines intent configuration, respecting repository toggles forenabledstatus 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.AgentequalsAgentClaude(repository override)cfg.AutoFix.Lintequals5(inherited from global)cfg.AutoFix.CIequals0(repository override)
Summary
no-mistakesloads 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_commandsare repository-only and never merged from global files. - The merge occurs in
internal/config/config.govia theMergefunction, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →