# How no-mistakes Merges Global and Repository Configuration

> Learn how no-mistakes merges global and repository configuration using a smart override strategy. Ensure secure and efficient settings management for your projects.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-01

---

**`no-mistakes` combines global defaults from `$HOME/.config/no-mistakes/config.yaml` with repository-specific [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.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`](https://github.com/kunchenguid/no-mistakes/blob/main/.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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config_merge_test.go) documents the precedence behavior:

```go
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`](https://github.com/kunchenguid/no-mistakes/blob/main/.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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) file (and validating this in [`config_merge_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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.