> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ziro-agent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Validating config

> Catch config mistakes in your editor and with ziro config doctor, and know what Ziro does with a value it cannot use.

Ziro's config loaders are forgiving: a value they cannot use falls back to its
default instead of stopping the agent. That keeps a session running, but it also
means a typo like `enabeld: true` can be ignored without anything on screen. Two
tools close that gap: JSON Schemas for your editor, and `ziro config doctor`.

## In your editor

Ziro publishes a JSON Schema for every config file at
`https://ziro-agent.com/schemas/v1/`. Every config file Ziro writes (`ziro init`,
`ziro agents`, `ziro migrate-config`, the capability manager) starts with a line
that points at its schema:

```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}}
# yaml-language-server: $schema=https://ziro-agent.com/schemas/v1/agent-settings.schema.json
meta:
  name: Coder
```

With a YAML language server installed (in VS Code, the Red Hat **YAML**
extension), that line gives you completion, hover text for every key, and a red
underline on a misspelled key or a wrong type. Add the line yourself to a file you
created by hand.

| File | Schema |
| - | - |
| `agents/<id>/settings.yaml` | `agent-settings.schema.json` |
| `<layer>/settings.yaml` | `layer-settings.schema.json` |
| a legacy split file, e.g. `shell_policy.yaml` | `sections/shell.schema.json` (one per section) |
| `agents/registry.yaml` | `registry.schema.json` |
| `flavours/<name>/flavour.yaml` | `flavour.schema.json` |
| `themes/<name>.yaml` | `theme.schema.json` |

The schemas accept what Ziro accepts: `${VAR}` tokens, a single string where a
list is expected, an MCP server without `transport` in a per-agent overlay, a
guardrails backend that leaves `type` to a lower layer.

## ziro config doctor

`ziro config doctor` checks every config file Ziro reads, across the project, user
and bundled layers, and prints each problem with its file, line, and what Ziro does
about it. It never changes a file.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
ziro config doctor                 # the project you are in (found from the current folder)
ziro config doctor --project ../x  # another project
ziro config doctor --json          # the same report as JSON, for scripts
```

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
.ziro/agents/coder/settings.yaml  (project)
  error   line 4  shell.enabeld: unknown key. Ignored.
  error   line 5  shell.timeout_s: Input should be a valid number, unable to parse string as a number, got 'soon'. This agent's config fails to load.

19 files checked: 2 errors, 0 warnings.
```

It exits **0** when there are no errors (warnings alone are fine), **1** on any
error, and **2** on a usage error, so it works as a CI step or a pre-commit hook.

It reports two kinds of issue.

**Errors**: the file says one thing and Ziro does another.

* A YAML syntax error, or a file that is not a mapping.
* An unknown key or section (a typo).
* A wrong type or an invalid value.
* A key written twice in the same mapping. YAML keeps the later one.
* An MCP server Ziro will not use: invalid `auth`, an `args` that is neither a
  list nor a string, or an unknown `transport`.
* A theme with a missing colour slot, or a colour that cannot be rendered.
* A flavour overlay for a policy file that does not exist.

**Warnings**: legal, but probably not what you meant.

* `registry.yaml` names a default agent that does not exist.
* A flavour lists an agent that does not exist.
* An untrusted project has `permissions`, `mcp`, `hooks` or `plugins` config
  (ignored until you trust it).
* An untrusted project sets `subagents.routing` or `subagents.model_defaults`
  (ignored), or a subagent or `resources` limit that can only tighten the lower layers.
* An untrusted project's subagent file replaces a user or bundled subagent
  (ignored) or sets `model`, `provider` or `reasoning_effort` (ignored).
* A legacy split file is not read because the same folder's `settings.yaml`
  already has that section.

> [!TIP]
>
> Run it after editing config by hand, and before reporting a bug: "this setting
> does nothing" is usually a typo the doctor names in one line.

## What Ziro does with a value it cannot use

The effect depends on the section. The doctor prints the same text.

| Where | An invalid value |
| - | - |
| `meta`, `persona`, `tools`, `mcp`, `registry.yaml`, layer `security` and `skills`, flavour fields, theme name and label | Ignored; the default is used. A warning goes to the log (`tui.log` under the TUI). |
| `meta.retry` | The agent's config fails to load. |
| `plugins` | Plugins are disabled for this agent. A `settings.yaml` that cannot be read or parsed has the same effect. |
| `modes` | Execution modes are disabled for this agent. |
| every other agent section (`shell`, `permissions`, `guardrails`, `compaction`, `fs`, ...) | The agent's config fails to load. |
| layer `resources` | Ziro fails when it reads that setting. |
| a value inside a flavour overlay | Ignored; the agent's own value applies. |
| a theme colour | The built-in Ziro colour is used for that slot. |

An unknown key is ignored everywhere except `plugins`, where any mistake disables
plugins for the agent: a broken plugin gate must never open up.

## Values Ziro reads generously

These are not errors, and neither the schema nor the doctor flags them:

* **One string for a list** of names: `denylist: web:web_fetch`,
  `allowlist: search`, `deny: "github*"`, `ref_extensions: md` each mean a
  one-item list. A number or mapping in such a list is read as text, so a mistake
  there still denies or exposes nothing extra.
* **Numbers in stdio MCP `args`** are passed as text: `args: [--port, 8080]`.
* **An MCP `auth` block without `type`** is OAuth. `auth: null` is the same as no
  `auth`.
* **An empty list** (`allow:` with nothing after it) is an empty list.
* **Text fields in `meta` and `persona`** accept numbers (`name: 2024`); a YAML
  false (`intro: no`) is read as unset.

## Next steps

> [card] [Configuration](https://docs.ziro-agent.com/guides/configuration.md)
>
> How layers resolve and merge.

> [card] [Agent file reference](https://docs.ziro-agent.com/reference/agent-files.md)
>
> Every section of `settings.yaml`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.