---
title: heretic-cli run
description: "Start agent containers — every flag, the resolution pipeline, what gets mounted and injected, sessions, container naming, root mode, build sidecars, and exit codes"
canonical: https://giglabo.com/heretic/docs/heretic-cli/commands/run
locale: en
---

# heretic-cli run

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

Start agent containers — every flag, the resolution pipeline, what gets mounted and injected, sessions, container naming, root mode, build sidecars, and exit codes

## Synopsis

```
heretic-cli run <agent-name> [command...]
    -d, --detach                       run the container in the background
    -s, --session <name>               session name (default: "default")
        --mcp <value>                  JSON string or path to a .json file (array of servers)
        --root                         run as root, keeping HOME=/home/agent
        --sidecar <runtime>            repeatable: node|python|java|go|rust
        --builder-image <rt>=<image>   repeatable builder image override
        --disable-sidecars             disable all build sidecars for this run

heretic-cli <agent-name> [-d] [-s <name>] [--root] [--sidecar <rt>]
                         [--builder-image <rt>=<img>] [--disable-sidecars] [-- <command...>]
```

The project directory is always the **current working directory** — `run` mounts it, hashes it into the container name, and stores it in the `heretic.project` label.

## Options

| Flag | Description |
|------|-------------|
| `-d, --detach` | start and return; prints the container ID and the `attach` hint. With the compose runner this becomes `docker compose up -d`. |
| `-s, --session <name>` | per-project namespace: session directory, container name, `heretic.session` label, compose project name, sidecar network. Names are sanitized (`[^a-zA-Z0-9_-]` → `-`). |
| `--mcp <value>` | replaces the profile's `mcp` array for this run. Value is a path to a `.json` file or an inline JSON **array** of server objects. |
| `--root` | one-off `extra.run_as_root: true`. Only applied when passed, so it never clobbers a profile value. |
| `--sidecar <runtime>` | replaces the profile's sidecar list for this run; each image defaults to `heretic-builder-<runtime>:latest`. |
| `--builder-image <rt>=<image>` | retargets one runtime's image, including sidecars declared in the profile. |
| `--disable-sidecars` | wins over `--sidecar` — sets an empty sidecar list. |
| `command...` | replaces the profile's `command` (the container `Cmd`). Its own flags are passed through untouched. |

## Resolution pipeline

```
1. collect CLI overrides            --mcp, --root, --sidecar / --builder-image / --disable-sidecars
2. resolveConfig()                  global profile → local override → CLI overrides
                                    + ${VAR} interpolation, secret scripts, mcp_file merge
                                    + post-merge validation
3. apply --builder-image            also to profile-declared sidecars
4. reject sidecars on runner: custom
5. create the runner                docker | compose | custom
6. runner.start()                   sidecars first, then the agent container
```

Info-level output: `Resolving config for '<agent>'…`, `Using global profile: <agent>`, `Starting agent '<agent>'…`. Local-override detection is logged at debug level (`-V`).

## Sessions

```bash
heretic-cli run claude -s feature-a
heretic-cli run claude -s feature-b     # runs at the same time, separate ~/.claude state
```

Two runs of the **same** session in the same directory collide: the second one stops and removes the first container. Sessions do not isolate the workspace — both sessions edit the same files. See [Sessions](https://giglabo.com/heretic/docs/heretic-cli/configuration/sessions).

## Container naming

```
heretic-<agent>-<session>-<hash8>
```

`hash8` is the first 8 hex characters of `sha256(<absolute project dir>)`, so the same profile in two checkouts produces two different containers.

## MCP override

```bash
heretic-cli run claude --mcp ./servers.json
heretic-cli run claude --mcp '[{"name":"fs","command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/workspace"]}]'
```

> **Warning: --mcp takes an array, not an mcpServers map**
>
> `--mcp` expects the `McpServer[]` shape. The `{"mcpServers": {…}}` / `{"servers": {…}}` map formats are what `heretic-cli agents mcp` and the profile's `mcp_file` accept. Bad input fails with `Invalid --mcp value: MCP config must be a JSON array of server objects`. `--mcp` is also **not** available in the shorthand form.

## Root mode

```bash
heretic-cli run claude --root
heretic-cli run --root claude       # identical — the flag is hoisted
```

With root mode the runner forces `User: root`, injects `HERETIC_RUN_AS_ROOT=1`, and bind-mounts the CLI's **embedded** `entrypoint.sh` over the image's `/entrypoint.sh`, so the flag works even on images built before the feature existed. The entrypoint then exports `HOME=/home/agent` and `USER=root`, which keeps every bind-mounted config (Claude settings, `.claude.json`, MCP, SSH keys) resolvable.

> **Note: docker exec shows HOME=/root**
>
> `docker exec` starts a fresh login that does not inherit the entrypoint's exports. The actual agent session (PID 1) has `HOME=/home/agent`.

## Build sidecars

```bash
heretic-cli run claude --sidecar node --sidecar python
heretic-cli run claude --builder-image node=ghcr.io/me/builder-node:1.2.3
heretic-cli run claude --disable-sidecars
```

Builders start **before** the agent, on a private per-run network, and the agent is wired to them through `BUILD_SIDECARS`. If a builder never becomes healthy the whole run is aborted and everything created is torn down. Full details: [Sidecars](https://giglabo.com/heretic/docs/heretic-cli/configuration/sidecars).

## Runner behaviour

| Runner | What `run` does |
|--------|-----------------|
| `docker` (default) | pings the daemon, pulls the image if missing, starts sidecars, creates the container, removes a stale container with the same name, then attaches (interactive) or returns the ID (detached) |
| `compose` | generates `.heretic/temp/<session>/compose.yaml` and runs `docker compose -f <file> -p heretic-<agent>-<session> up [-d]` |
| `custom` | runs your `.heretic/cli/compose.yaml` verbatim, exporting resolved secrets and env into the compose process environment |

Interactive runs shell out to `docker attach`, so the **docker CLI must be installed** — the daemon socket alone is not enough. Right after start the runner fixes ownership of the mounted agent home (`chown -R agent:agent`), which is what makes Docker Desktop bind mounts writable for uid 1000.

See [Runners](https://giglabo.com/heretic/docs/heretic-cli/configuration/runners) for the full comparison.

## What lands in the container

**Environment** — resolved secrets (docker runner: only those also named in `env`) → profile `env` → transformations → GitHub/Copilot tokens → `git.*` → `HERETIC_RUN_AS_ROOT` → `BUILD_SIDECARS` / `SIDECAR_ENV_PASSTHROUGH` → `SSH_*`. Variable **names** are logged at info level; values only at debug level, masked.

**Mounts**

```
profile volumes                              (e.g. ${CWD} → /workspace)
.heretic/temp/<session>/entrypoint.sh   → /entrypoint.sh:ro       (root mode only)
<ssh.key_path>                          → /home/agent/.ssh/id_rsa:ro
generated .mcp.json                     → /workspace/.mcp.json    (or both .copilot paths)
/var/run/docker.sock                    → /var/run/docker.sock    (dind: true)
.heretic/temp/<session>                 → /home/agent/.claude  and /root/.claude
.heretic/temp/<session>/.claude.json    → /home/agent/.claude.json and /root/.claude.json
```

**Labels** — `heretic.managed=true`, `heretic.agent`, `heretic.project`, `heretic.session`, plus `heretic.network` when sidecars are used. `extra.labels` is applied last and can override them.

## Examples

```bash
heretic-cli run claude                          # interactive, session "default"
heretic-cli claude                              # identical (shorthand)
heretic-cli run claude -s feature-x             # separate session
heretic-cli run claude -d && heretic-cli ps     # background, then list
heretic-cli run claude -- bash -lc 'npm ci && npm test'
heretic-cli run claude --root
heretic-cli run claude --sidecar node --sidecar python
heretic-cli run claude --mcp ./mcp-servers.json
heretic-cli -V run claude                       # verbose (before the subcommand)
```

## Errors and exit codes

| Situation | Message | Exit |
|-----------|---------|------|
| unknown profile | `Profile '<x>' not found.` + `Run 'heretic-cli agents list' …` | 1 |
| invalid profile YAML/shape | `Invalid profile '<x>': …` | 1 |
| override for another profile | `Local config extends 'a' but loading profile 'b'` | 1 |
| all-comments local override | `Failed to load local config from …: Invalid config: expected an object` | 1 |
| post-merge validation | `Config validation failed: <errors>` | 1 |
| Docker unavailable | `Docker is not available. Start Docker and try again.` | 1 |
| bad `--mcp` | `Invalid --mcp value: …` | 1 |
| bad `--builder-image` | `--builder-image expects <runtime>=<image>, got '…'` | 1 |
| sidecars + `runner: custom` | `Build sidecars require the 'docker' or 'compose' runner …` | 1 |
| builder never healthy | `build sidecar '<name>' did not become healthy within <n>s` | 1 |
| container finished | `Container exited with code <n>` (logged when non-zero) | container's code |

## Gotchas

- A local override **replaces** the whole `volumes` array — adding one mount means re-listing all of them.
- Volume sources must be absolute after interpolation; use `${CWD}` and `${HOME}`.
- Interactive mode needs a TTY. In CI use `-d` plus `docker logs`, or pass an explicit command.
- The session directory is created with mode `0777` so any container uid can write to it; treat it as scratch — `.mcp.json`, `settings.json`, `compose.yaml` and `entrypoint.sh` are regenerated every run.
- There is no `--dry-run`; preview with `heretic-cli agents show <name> --resolved` or `heretic-cli local-validate <name>`.

## Next Steps

- [Sessions](https://giglabo.com/heretic/docs/heretic-cli/configuration/sessions) — parallel instances
- [Runners](https://giglabo.com/heretic/docs/heretic-cli/configuration/runners) — docker vs compose vs custom
- [Sidecars](https://giglabo.com/heretic/docs/heretic-cli/configuration/sidecars) — build toolchains in sibling containers
- [Merge Rules](https://giglabo.com/heretic/docs/heretic-cli/reference/merge-rules) — how the layers combine

## Related

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