---
title: heretic-cli local-init
description: "Scaffold per-project configuration — per-profile overrides, the compose template, local Claude settings merge, and validating the result with local-validate"
canonical: https://giglabo.com/heretic/docs/heretic-cli/commands/local-init
locale: en
---

# heretic-cli local-init

> Markdown twin of https://giglabo.com/heretic/docs/heretic-cli/commands/local-init
> 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

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`.

```bash
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.`

> **Warning: 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

```json
{
  "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

> **Warning: 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.
>
> ```yaml
> 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 |

```json
{
  "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](https://giglabo.com/heretic/docs/heretic-cli/configuration/runners).

## `local-validate`

```bash
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.

> **Warning: 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](https://giglabo.com/heretic/docs/heretic-cli/configuration/local-overrides) — what you can override and how
- [Merge Rules](https://giglabo.com/heretic/docs/heretic-cli/reference/merge-rules) — per-field merge behaviour
- [Validation](https://giglabo.com/heretic/docs/heretic-cli/reference/validation) — the complete error list

## Related

- HTML version of this page: https://giglabo.com/heretic/docs/heretic-cli/commands/local-init
- Site map for agents: https://giglabo.com/llms.txt
