heretic-cli local-init
Scaffold per-project configuration — per-profile overrides, the compose template, local Claude settings merge, and validating the result with local-validate
Synopsis
heretic-cli local-init [profile] [--compose] [-f|--force]
heretic-cli local-validate [profile]
| Flag | Description |
|---|---|
--compose | write .heretic/cli/compose.yaml (the file used verbatim by runner: custom) instead of a per-profile override; the profile argument is ignored |
-f, --force | overwrite existing files |
Called without a profile argument, local-init lists your global profiles and asks you to pick one. With an argument it checks that the profile exists, otherwise Profile '<x>' not found in ~/.heretic/agents/ + Available profiles: … and exit 1. It refuses to overwrite without --force: File already exists: <path> + Use --force to overwrite.
heretic-cli local-init claude # → .heretic/cli/claude.yaml
heretic-cli local-init copilot # a second profile in the same project
heretic-cli local-init --compose # → .heretic/cli/compose.yaml
heretic-cli local-init claude --force # regenerate the templates
What it creates
<project>/.heretic/
├── cli/
│ ├── <profile>.yaml per-profile override (a fully commented template)
│ └── claude-settings.json local Claude Code settings, merged at run time
└── temp/ session directories are created on demand by the runners
Existing files are kept unless --force is given. If a legacy .heretic/cli/agent.yaml is present you also get: Note: Legacy .heretic/cli/agent.yaml exists. Per-profile file <x>.yaml takes precedence.
Add .heretic/cli/ to .gitignore
The command suggests this whenever a .gitignore exists and does not already list it — local overrides contain machine-specific paths and secret references.
The generated Claude settings template
{
"dangerouslySkipPermissions": true,
"enabledMcpjsonServers": [],
"allowedTools": ["Bash", "Edit", "Write", "Read", "Glob", "Grep", "WebFetch", "WebSearch", "mcp__*"]
}
The provider (anthropic, thirdparty or copilot, detected from the profile) only changes the log label — the template itself is identical for all three.
The all-comments trap
A fresh override file breaks run until you edit it
The generated <profile>.yaml contains only comments, so YAML parses it as null. Because the file exists, heretic tries to load it and fails:
$ heretic-cli local-validate
ERROR: Unexpected error: Failed to load local config from …/.heretic/cli/claude.yaml: Invalid config: expected an object
$ heretic-cli run claude
ERROR: Failed to run agent 'claude': Failed to load local config from …/.heretic/cli/claude.yaml: Invalid config: expected an object
Fix: uncomment at least one key, or start from a minimal file. Deleting the file also works — the global profile alone is a complete configuration.
extends: claude
env:
PROJECT_NAME: acme-api
Local Claude settings merge
.heretic/cli/claude-settings.json is detected automatically and merged over the profile's global settings file at run time; the result is written into the session directory and appears as ~/.claude/settings.json inside the container.
| Value type | Strategy |
|---|---|
Arrays (permissions.allow, permissions.deny, allowedTools) | union — combined and de-duplicated |
Objects (env) | shallow merge; local wins per key |
| Primitives | local replaces global |
{
"permissions": {
"allow": ["mcp__project-specific"],
"deny": ["Bash(rm -rf *)"]
},
"env": {
"ANTHROPIC_DEFAULT_SONNET_MODEL": "different-model"
}
}
If the profile's global settings file is missing, a warning is logged and the merge is skipped.
Compose mode
heretic-cli local-init --compose writes a .heretic/cli/compose.yaml containing an agent service (giglabo/claude-heretic:latest, .:/workspace, ANTHROPIC_API_KEY passthrough, stdin_open, tty, working_dir: /workspace, network_mode: host) plus commented db and redis examples.
That file is what runner: custom executes verbatim — heretic injects nothing into it, but it does export the resolved secrets and environment into the docker compose process so ${VAR} references inside the file resolve. See Runners.
local-validate
heretic-cli local-validate # every local config in .heretic/cli/
heretic-cli local-validate claude # just this one
Per profile it checks that the file exists, loads it (for a per-profile file extends is inferred from the filename; a legacy agent.yaml must declare it), resolves the full configuration — which runs secret scripts — validates the result, prints a summary, and then dumps the entire resolved object:
Validating local config for profile "claude"...
Extends: claude
Image: giglabo/claude-heretic:latest
Runner: docker
Volumes: 1 mount(s)
Env: 1 variable(s)
✓ All checks passed.
Resolved configuration:
────────────────────────────────────────────────────────────
name: claude
projectDir: /abs/project
sessionName: default
image: giglabo/claude-heretic:latest
runner: docker
agentType: claude
provider: anthropic
volumes: [...]
env: {...}
────────────────────────────────────────────────────────────
With no local configs at all: No local config files found in .heretic/cli/ + Run 'heretic-cli local-init <profile>' to create one. and exit 1. Exit code is 0 only when every discovered config passes.
The dump contains resolved secrets in plaintext
local-validate is the most complete view of a configuration — more complete than agents show --resolved — precisely because it prints resolved secret values. Do not paste it into tickets or CI logs.
Reserved filenames in .heretic/cli/ are compose.yaml and claude-settings.json; every other *.yaml / *.yml file is treated as a profile override, and agent.yaml resolves to whatever its extends: names.
Next Steps
- Local Overrides — what you can override and how
- Merge Rules — per-field merge behaviour
- Validation — the complete error list