---
title: Merge Rules
description: "Field-by-field precedence across the three configuration layers — what replaces, what merges shallowly, what merges deeply, plus interpolation order and the Claude settings merge"
canonical: https://giglabo.com/heretic/docs/heretic-cli/reference/merge-rules
locale: en
---

# Merge Rules

> Markdown twin of https://giglabo.com/heretic/docs/heretic-cli/reference/merge-rules
> Fetch this instead of the HTML page: same content, a fraction of the bytes.
> Site structure and the full page list for agents: https://giglabo.com/llms.txt

Field-by-field precedence across the three configuration layers — what replaces, what merges shallowly, what merges deeply, plus interpolation order and the Claude settings merge

## Order of operations

```
global profile  ~/.heretic/agents/<name>.yaml
      ↓  merge
local override  .heretic/cli/<name>.yaml   (or legacy agent.yaml with a matching extends)
      ↓  merge
CLI overrides   --mcp, --root, --sidecar / --builder-image / --disable-sidecars
      ↓
secrets resolved   (scripts run, $VAR references read)
      ↓
${VAR} interpolation over every string in the merged object
      ↓
mcp_file merged with inline mcp (by name)
      ↓
defaults applied → validation
```

Two consequences worth internalising: interpolation happens **after** merging, so an override can introduce a `${VAR}` the profile never used; and validation runs **last**, on the fully merged and interpolated object.

## Field strategies

| Field | Strategy | Notes |
|-------|----------|-------|
| `image` | replace | |
| `runner` | replace | `docker`, `compose`, `custom` |
| `agent_type` | replace | `claude`, `aider`, `copilot-cli`, `generic` |
| `provider` | replace | `anthropic`, `thirdparty`, `copilot` |
| `volumes` | **replace** | the whole array — re-list every mount |
| `env` | shallow merge | per key; later layer wins |
| `workdir` | replace | |
| `command` | replace | normalised to an array |
| `interactive` | replace | |
| `tty` | replace | |
| `extra` | shallow merge | per key, with the exceptions below |
| `extra.ports` | **replace** | all-or-nothing |
| `extra.capabilities` | **replace** | all-or-nothing |
| `extra.labels` | shallow merge | per label |
| `compose` | deep merge | objects recurse; arrays and primitives replace |
| `ssh` | shallow merge | per key |
| `tool_backends` | shallow merge, `sidecars` replaced | scalars such as `workspace_target` and `ready_timeout` merge |
| `mcp` | **replace** | the whole array |
| `mcp_file` | replace | |
| `mcp_override` | replace | |
| `git` | shallow merge | per key |
| `dind` | replace | |
| `secrets` | shallow merge | per key |
| `claude_settings` | replace | |

## Strategy definitions

**Replace** — the later value wins entirely. If the override sets `image`, the profile's `image` is ignored.

**Shallow merge** — top-level keys are combined; on a conflict the later layer wins. Nested objects are not merged recursively.

```yaml
# global profile
env:
  NODE_ENV: production
  API_URL: https://api.example.com

# local override
env:
  NODE_ENV: development
  DEBUG: "app:*"

# result
env:
  NODE_ENV: development                 # override wins
  API_URL: https://api.example.com      # kept from the profile
  DEBUG: "app:*"                        # added
```

**Deep merge** — objects recurse at every level; arrays and primitives still replace. Used only for `compose`.

## The replace traps

```yaml
# WRONG — the workspace mount disappears
extends: claude
volumes:
  - { source: "${HOME}/.gitconfig", target: /home/agent/.gitconfig, readonly: true }

# RIGHT — re-list everything you need
extends: claude
volumes:
  - { source: "${CWD}", target: /workspace }
  - { source: "${HOME}/.gitconfig", target: /home/agent/.gitconfig, readonly: true }
```

The same applies to `mcp` (add one server locally and the profile's servers are gone), `extra.ports` and `extra.capabilities`. `env`, `secrets`, `git`, `ssh` and `extra` are additive, so single-key changes are safe there.

## CLI overrides

| Flag | Effect on the merged config |
|------|-----------------------------|
| `--mcp <value>` | replaces `mcp` |
| `--root` | sets `extra.run_as_root: true` — only when passed, so it never clears a profile value |
| `--sidecar <rt>` | replaces `tool_backends.sidecars` |
| `--builder-image <rt>=<img>` | rewrites the image of that runtime, including sidecars declared in the profile |
| `--disable-sidecars` | sets `tool_backends.sidecars: []`, winning over `--sidecar` |

## Interpolation context

`${VAR}` is resolved from, in this order of specificity: `CWD` (the project directory), `HOME`, every variable in the host environment, and every resolved secret.

- an unknown variable produces `""` and a warning
- an empty value is then stripped from the container environment, unless the key was explicitly `""` in `env`
- `$${LITERAL}` escapes to `${LITERAL}`
- a bare `$VAR` is left untouched in configuration values (it *is* meaningful inside `secrets:`)

## Claude settings merge

The profile's `claude_settings` file and the project's `.heretic/cli/claude-settings.json` follow their own rules, and the result is written into the session directory:

| Value type | Strategy |
|------------|----------|
| arrays (`permissions.allow`, `permissions.deny`, `allowedTools`) | union, de-duplicated |
| objects (`env`) | shallow merge; local wins |
| primitives | local replaces global |

## Verifying the result

```bash
heretic-cli agents show <name> --resolved     # merged, interpolated, secrets masked
heretic-cli local-validate <name>             # the complete resolved object
heretic-cli -V run <name>                     # verbose: which layers were used
```

## Next Steps

- [Local Overrides](https://giglabo.com/heretic/docs/heretic-cli/configuration/local-overrides) — practical override recipes
- [Validation](https://giglabo.com/heretic/docs/heretic-cli/reference/validation) — the rules applied after merging
- [Environment Variables](https://giglabo.com/heretic/docs/heretic-cli/reference/environment-variables) — what the container ends up with

## Related

- HTML version of this page: https://giglabo.com/heretic/docs/heretic-cli/reference/merge-rules
- Site map for agents: https://giglabo.com/llms.txt
