Validation
Every validation rule heretic-cli applies — profile shape checks versus post-merge checks, the exact error messages, which commands run which stage, and how to read the results
Validation happens in two stages, and knowing which stage produced an error tells you where to look.
| Stage | Checks | Run by |
|---|---|---|
| Shape (a single file) | types, required fields, enum values, port syntax | agents validate, and every command that loads a profile |
| Post-merge (the resolved config) | absolute paths, cross-field consistency, sidecar wiring, environment value types | run, local-validate, doctor (local configs) |
The shape stage never contacts Docker, never interpolates ${VAR} and never runs secret scripts. The post-merge stage does resolve secrets, because it validates the values that will actually reach the container.
Stage 1 — profile shape
$ heretic-cli agents validate claude
Validating profile "claude"...
[pass] YAML syntax
[pass] Required fields
[pass] All validation checks passed
| Rule | Requirement |
|---|---|
image | required, non-empty string |
runner | required; one of docker, compose, custom |
agent_type | one of claude, aider, copilot-cli, generic |
provider | one of anthropic, thirdparty, copilot |
volumes[i].source / .target | required non-empty strings; readonly must be boolean |
env values | strings |
extra.ports[i] | must match host:container, optionally with a protocol suffix |
ssh.host | required whenever an ssh block is present |
mcp[i] | name plus command (stdio) or url (http) |
secrets.* | non-empty strings |
Exit code is 1 if any check fails. A [warn] alone does not fail the command; the only warning implemented is:
[warn] compose section present but runner is "docker". Compose config will be ignored.
Called without a name, agents validate walks every file in ~/.heretic/agents/. A YAML syntax error prints [fail] YAML syntax: …; for a single named profile it stops there, in all-profiles mode it continues with the next file.
A profile missing from agents list is usually invalid
agents list skips invalid profiles with a warning rather than failing. If a profile does not show up, run heretic-cli agents validate.
Stage 2 — resolved configuration
Run automatically before every container start, and on demand with heretic-cli local-validate. All failures are collected and thrown together:
Config validation failed: <error>, <error>, …
Core
Image must be non-emptyInvalid runner type: <x> (must be one of: docker, compose, custom)Invalid agent type: <x> (must be one of: claude, aider, copilot-cli, generic)
Volumes
Volume[i].source must be non-emptyVolume[i].source must be an absolute path: <path>Volume[i].target must be non-empty
Environment
Environment variable '<key>' must be a string (got <type>)— a barePORT: 8080is a number in YAML; quote it as"8080"
SSH
ssh.host must be non-emptyssh.port must be between 1 and 65535ssh.key_path must be an absolute path: <path>—~/…fails here, use${HOME}/…
Build sidecars
tool_backends.sidecars[i].runtime '<x>' is invalid (must be one of: node, python, java, go, rust)tool_backends.sidecars[i]: duplicate runtime '<x>'tool_backends.sidecars[i].image must be non-emptytool_backends.sidecars[i].port must be between 1 and 65535tool_backends.ready_timeout must be a positive number of secondstool_backends: no volume targets '<workspace_target>' — build sidecars need the workspace bind-mounted
MCP
mcp[i].name must be non-emptymcp[i].command must be non-emptymcp[i].url must be non-empty for http transport
Git
git.token must be non-empty when specified
Non-fatal warning — both tool_backends.sidecars and ssh configured: a single build would then span two filesystems.
Errors from loading, not validating
| Message | Meaning |
|---|---|
Profile not found: <name> | no ~/.heretic/agents/<name>.yaml |
Invalid profile '<name>': <errors> | stage 1 rejected the file |
Failed to load profile '<name>': … | YAML parse or I/O failure |
Local config extends 'a' but loading profile 'b' | the override belongs to another profile |
Failed to load local config from <path>: Invalid config: expected an object | the file exists but parses to null — typically the all-comments local-init template |
Secret script not found: <path> | a secrets: entry points at a missing script |
Secret script failed (<path>): <stderr> | the script exited non-zero |
Secret "X": environment variable "Y" is not set | a $VAR secret reference with nothing behind it |
MCP file not found: <path> | mcp_file points nowhere |
Running validation
heretic-cli agents validate # every global profile (stage 1)
heretic-cli agents validate claude # one profile
heretic-cli local-validate # every local config (stage 1 + 2 + resolved dump)
heretic-cli local-validate claude # one local config
heretic-cli agents show claude --resolved # the resolved config, secrets masked
heretic-cli -V run claude # verbose resolution while starting
Remember that -V/--verbose is a program-level flag and must precede the subcommand — run claude --verbose passes --verbose to the container.
doctor does not validate global profiles in 0.1.0
The profile and image checks in heretic-cli doctor report 0 profile(s) validated regardless of your configuration. Use agents validate and local-validate — see doctor.
Pre-flight checklist before a first run
heretic-cli agents validate <name> # shape
heretic-cli agents show <name> --resolved # interpolation and absolute paths
heretic-cli local-validate <name> # full resolution incl. secrets and sidecars
docker images | grep <image> # is the image available
Next Steps
- Merge Rules — what the validator sees after merging
- Troubleshooting — symptom-first fixes
- Agent Profiles — the schema being validated