# Global vs Repo Config in no-mistakes: How Configuration Merging Works

> Understand global vs repo config in no mistakes. Learn how no mistakes merges user wide defaults with repo specific overrides and how repository settings take precedence.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: deep-dive
- Published: 2026-06-27

---

**no-mistakes distinguishes global user-wide defaults from repository-specific overrides, merging them through a hierarchical overlay where repo configurations take precedence over global settings while maintaining security through trusted-source validation.**

The no-mistakes tool uses a two-tier configuration system to balance user preferences with project-specific requirements. Understanding how **global and repo config in no-mistakes** interact ensures you can set organization-wide defaults while allowing individual repositories to customize behavior safely. Both configurations are YAML files parsed into Go structs and combined into a single runtime `Config` that drives the pipeline.

## Where Global and Repo Configurations Live

### Global Configuration Path and Structure

The **global configuration** resides at `~/.no-mistakes/config.yaml` and is created automatically by `EnsureDefaultGlobalConfig`. This file holds defaults that apply to all repositories a user works with, including default agent settings, auto-fix limits, log levels, and CI timeouts.

In [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go), the `GlobalConfig` struct is defined around lines 40-52, containing fields such as `agent`, `acp_path`, `ci_timeout`, and `log_level`.

### Repo Configuration Path and Structure

The **repo configuration** lives at `<repo-root>/.no-mistakes.yaml` and uses the `RepoConfig` struct defined around lines 69-84 in the same file. This scope allows repositories to customize behavior through custom commands, ignore patterns, per-repo agents, and fine-grained auto-fix settings.

## What Each Configuration Scope Controls

### Global-Only Settings

Certain fields exist only in the global scope and affect daemon behavior across all projects:

- **`ci_timeout`** – Controls CI operation timeouts
- **`log_level`** – Determines logging verbosity
- **`acp_path`** / **`acp_registry_overrides`** / **`agent_path_override`** / **`agent_args_override`** – Global defaults for ACP agents and native-agent binary overrides

### Repo-Only Settings

These fields are exclusive to repository configurations and enable project-specific customization:

- **`commands`** – Explicit shell commands for `lint`, `test`, and `format` operations
- **`ignore_patterns`** – File globs to exclude from processing
- **`allow_repo_commands`** – Security flag determining whether to trust the pushed copy or default-branch copy of the config for code-executing fields

### Overridable Settings

Both scopes can declare these values, with repo configurations taking precedence when non-nil:

- **`agent`** – Repository agent overrides global default when non-empty
- **`auto_fix`** – Auto-fix limits and behaviors
- **`intent`** – Intent detection settings
- **`test`** – Test execution parameters

## Loading Configurations from Disk

The [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) file provides distinct loader functions for each scope. `LoadGlobal` begins at line 66 and parses the YAML from the user's home directory, falling back to defaults if the file is missing. `LoadRepo` starts at line 50 and loads the configuration from the repository root, returning an empty struct if no file exists.

```go
// Load global config with fallback defaults
global, err := config.LoadGlobal(filepath.Join(os.Getenv("HOME"), ".no-mistakes", "config.yaml"))
if err != nil {
    log.Fatal(err)
}

// Load repo config from worktree root
repo, err := config.LoadRepo(repoRootPath)
if err != nil {
    log.Fatal(err)
}

```

## The Merge Algorithm: How Global and Repo Config Combine

The central `Merge(global, repo) *Config` function, implemented in lines 50-85 of [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go), implements a hierarchical overlay strategy:

1. **Initialize defaults** – Start with `autoFixDefaults()`, `intentDefaults()`, and `testDefaults()` baseline values
2. **Apply global overrides** – `applyAutoFixOverrides`, `applyIntentOverrides`, and `applyTestOverrides` overlay non-nil fields from the global config
3. **Apply repo overrides** – The same helper functions process the repo config, allowing repository values to win
4. **Assemble the Config struct** – Most fields (agent, ACP settings, CI timeout, log level) transfer directly from the global config
5. **Copy repo-only fields** – `Commands` and `IgnorePatterns` transfer verbatim from the repo config
6. **Resolve agent precedence** – If `repo.Agent` is non-empty, it replaces the global-derived agent (lines 80-82)

The function returns a unified `*Config` pointer that the pipeline uses for execution.

## Security: Trusted vs Pushed Configuration

The `EffectiveRepoConfig` function (lines 86-102) implements a security boundary for code-executing fields. By default, the daemon only trusts the **default-branch** copy of [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) rather than the **pushed** copy from contributors. This prevents supply-chain attacks where malicious shell commands or agent swaps could be injected.

When `allow_repo_commands` is false (the default), the effective configuration draws `Commands` and `Agent` exclusively from the trusted default-branch copy, ignoring potentially malicious values from pull requests.

```go
trusted, _ := config.LoadRepoFromBytes(trustedYAML)   // default-branch copy
pushed, _ := config.LoadRepoFromBytes(pushedYAML)     // contributor copy

effective := config.EffectiveRepoConfig(pushed, trusted, false)
// effective.Commands and effective.Agent now come only from trusted source

```

## Working with Configurations in Code

### Loading and Merging Configurations

```go
package main

import (
    "fmt"
    "os"
    "path/filepath"

    "github.com/kunchenguid/no-mistakes/internal/config"
)

func main() {
    // Load global configuration
    globalPath := filepath.Join(os.Getenv("HOME"), ".no-mistakes", "config.yaml")
    global, err := config.LoadGlobal(globalPath)
    if err != nil {
        panic(err)
    }

    // Load repository configuration
    repoRoot, _ := os.Getwd()
    repo, err := config.LoadRepo(repoRoot)
    if err != nil {
        panic(err)
    }

    // Merge into final runtime configuration
    finalCfg := config.Merge(global, repo)

    fmt.Printf("Effective agent: %s\n", finalCfg.Agent)
    fmt.Printf("Auto-fix lint limit: %d\n", finalCfg.AutoFix.Lint)
    fmt.Printf("Repo commands: %s %s %s\n",
        finalCfg.Commands.Lint, finalCfg.Commands.Test, finalCfg.Commands.Format)
}

```

### Validating Security-Effective Configuration

```go
trusted, _ := config.LoadRepoFromBytes(trustedYAML)
pushed, _ := config.LoadRepoFromBytes(pushedYAML)

effective := config.EffectiveRepoConfig(pushed, trusted, false)

fmt.Printf("Effective agent: %s\n", effective.Agent)
fmt.Printf("Effective lint command: %s\n", effective.Commands.Lint)

```

## Summary

- **Global configuration** (`~/.no-mistakes/config.yaml`) provides user-wide defaults for agents, timeouts, and logging via the `GlobalConfig` struct
- **Repo configuration** ([`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml)) enables per-repository customization of commands, ignore patterns, and agent overrides via the `RepoConfig` struct
- **The merge process** initializes defaults, overlays global values, then overlays repo values, with repo configurations taking precedence for non-nil fields
- **Security protection** ensures that code-executing fields (`commands`, `agent`) come only from the trusted default-branch copy unless `allow_repo_commands` is explicitly enabled
- **Key files** implementing this logic reside in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) with specific functions at lines 50 (`LoadRepo`), 66 (`LoadGlobal`), and 50-85 (`Merge`)

## Frequently Asked Questions

### What takes precedence when the same setting exists in both global and repo config?

Repository configurations take precedence over global settings when the repo value is non-nil. The `Merge` function applies global overrides first, then overlays repo values, ensuring that repository-specific intents, auto-fix limits, and agent settings silently win while shared defaults remain consistent.

### Can repository configurations execute arbitrary shell commands?

Yes, but only from trusted sources. The `commands` field (specifying `lint`, `test`, and `format` shells) is repo-only and can execute arbitrary commands. However, by default, `no-mistakes` only uses the default-branch copy of [`.no-mistakes.yaml`](https://github.com/kunchenguid/no-mistakes/blob/main/.no-mistakes.yaml) for these fields, not the pushed copy from contributors, unless the maintainer explicitly sets `allow_repo_commands` to true.

### Where does no-mistakes store global configuration files?

The global configuration stores at `~/.no-mistakes/config.yaml` in the user's home directory. The `LoadGlobal` function in [`internal/config/config.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/config/config.go) (line 66) reads this path, and the tool creates it automatically via `EnsureDefaultGlobalConfig` if missing.

### How does the merge algorithm handle nil or empty values?

The merge algorithm treats nil values as "not specified" and preserves existing values from previous layers. During the overlay process (lines 50-85), helper functions like `applyAutoFixOverrides` only replace values when the source configuration provides non-nil fields, ensuring that empty repo settings do not accidentally clear valid global defaults.