# CLI reference

```text
muhar -spec <file> [-j <n>] [-shard <i>/<n>]
```

muhar uses Go's standard flag syntax: `-j 2`, `-j=2` and `--j 2` are equivalent. Flags must come before any positional argument; muhar takes none.

## Flags

| Flag | Default | Meaning |
|---|---|---|
| `-spec <file>` | none, required | Path to the [spec](spec.md) JSON, relative to the current directory. Missing: `muhar: -spec is required`, exit 1. |
| `-j <n>` | `1` | Mutations in flight at once. `1` runs sequentially, in place. With at least two effective workers, each gets its own copy of the tree. `0` sizes to the machine, capped at 2. Negative: `muhar: -j must be >= 0`, exit 1. |
| `-shard <i>/<n>` | none: run every mutation | Run only this invocation's share: the mutations at spec index ≡ i (mod n), 0-based. Baseline and control still run in full. |
| `-h`, `-help` |  | Print usage to stderr and exit 0. |

## -j: parallel workers

A sweep is one suite run per mutation, so the suite is almost the whole cost. With `-j` above 1 and at least two mutations, the mutations are dealt to a pool of workers. Each worker owns an isolated copy of the tree, so no two workers ever edit the same file; sharing one would make every verdict depend on timing.

| Value | Workers |
|---|---|
| `1` (default) | One, in place, in `root`. No copies. |
| `n` above 1 | At most `n`, capped at the number of mutations. Two or more get their own copies of `root` in the system temp directory; one runs in place. |
| `0` | One per CPU, capped at 2. On a one-CPU machine, one. |

When the spec (or shard) has fewer mutations than `-j`, muhar uses that many workers; with one, it runs in place like `-j 1`. This also means an interrupted run may leave the original file mutated ([signals and cleanup](internals.md#signals)), even when the requested `-j` is above 1.

Nothing about the verdicts changes with `-j`. The baseline and control still run first, in the first worker's copy when at least two workers are used, or in place otherwise, so the copy itself is proven sound before any verdict rests on it. Results are reported in spec order, whichever worker finished first. The report on stdout is byte-identical to a sequential run's.

With `-j` above 1 muhar prints one line to stderr naming the tree it copies:

```text
muhar: 2 mutations in flight, one isolated copy of /home/dev/limits per worker
```

The number is the requested pool size; with fewer mutations than that, fewer workers start. The notice is still printed when the effective count is one and execution falls back to running in place.

### Why -j 0 is capped at 2

Every worker holds a full copy of the tree and runs its own suite process, so peak memory grows with the number of mutations in flight, not with the size of the spec. Sizing the pool to the CPU count on a many-core CI runner put enough copies and builds in memory at once to exceed the runner's memory limit and kill the sweep. Capping `-j 0` at 2 bounds peak memory by construction. An explicit `-j <n>` is used as given: a caller who asks for 8 is making their own memory trade.

### Copies and Go modules

A copied `go.mod` with a relative `replace` directive pointing outside the tree would resolve to nothing in the temp directory, and every suite run in the copy would fail. muhar rewrites those directives to absolute paths in each copy and re-reads the file to prove the rewrite landed. This needs `go` on `PATH`. Details: [How a run works](internals.md#module-replace).

Use relative `file` paths with `-j` above 1. An absolute `file` points at the original tree, not the worker's copy ([spec reference](spec.md#absolute-paths)).

## -shard: splitting a spec across machines

`-shard i/n` runs the mutations whose 0-based index in the spec satisfies `index mod n = i`. For a fixed `n`, the `n` shards are pairwise disjoint and their union is the whole spec: every mutation runs exactly once across the set. Round-robin by index, rather than contiguous blocks, spreads neighbouring mutations — which tend to hit the same file and cost about the same — evenly across shards. muhar's test `TestShardSelectPartitions` holds the partition property.

Figure: Nine mutations split with -shard i/3. Each shard runs the baseline and the control in full, then only its own mutations.

Shard 0/3 runs mutation indices 0, 3, 6.
Shard 1/3 runs mutation indices 1, 4, 7.
Shard 2/3 runs mutation indices 2, 5, 8.
Every shard also runs the baseline and the positive control.

Each shard is independently trustworthy: it proves the baseline and the control before reporting anything, so a broken harness fails every shard, not one of them. Its `Totals:` line covers its own mutations only. A shard's report is a partial sweep by design, and muhar says so on stderr:

```text
muhar: shard 0/2 — running 2 of 3 mutations (indices ≡ 0 mod 2); the other shards own the rest
```

Running all `n` shards is the caller's job. [CI integration](ci.md#sharded) shows a matrix that does it.

`-shard` and `-j` combine: the shard is selected first, then its mutations are dealt to `-j` workers.

### Shard errors

All exit 1. A malformed shard is refused; it never degrades to running everything or nothing.

| Input | Message |
|---|---|
| `-shard 1` | `muhar: -shard must be "i/n" (0-based shard i of n), got "1"` |
| `-shard x/3` | `muhar: -shard "x/3": shard index "x" is not an integer` |
| `-shard 0/x` | `muhar: -shard "0/x": shard count "x" is not an integer` |
| `-shard 0/0` | `muhar: -shard "0/0": shard count must be >= 1` |
| `-shard 2/2` | `muhar: -shard "2/2": shard index must satisfy 0 <= i < n` |

## Streams

| Stream | Carries |
|---|---|
| stdout | The report only: the health line, one line per mutation, `Totals:`, and the `NOTE:` line when any mutation could not be planted. Or, for a broken run, `HARNESS BROKEN — run discarded.` and the reason. |
| stderr | muhar's own notices (`-j` and `-shard` lines, errors) and everything the suite prints, on both its stdout and stderr, in run order. |

Capture stdout to gate on; send stderr to the log. `report=$(muhar -spec mutations.json)` does exactly that in a shell.

## Environment

The suite inherits muhar's environment with one change: `GOCACHE` is set to a fresh directory created for this run and removed when it ends. Any `GOCACHE` you set is overridden for the suite. `GOMODCACHE` is not touched, so module downloads are shared and not repeated.

The temp directory for worker copies and the build cache is the system default, `TMPDIR` on Unix. Point `TMPDIR` at a disk with room for `-j` copies of the tree.

Why the per-run cache exists is explained in [How a run works](internals.md#gocache).

## Exit status

| Status | Meaning |
|---|---|
| `0` | Harness healthy. Read the report for verdicts. |
| `1` | Usage, spec or I/O error. No suite ran, or setup failed. |
| `2` | `HARNESS BROKEN`: baseline red, control not caught, or a worker copy could not be made. Also: an unknown flag or malformed flag value, from Go's flag parser. |
| `130` | Interrupted by `SIGINT` or `SIGTERM`. |

The full table, and how to gate on it, is in [Output and exit codes](output.md#exit-codes).

---

Source of this page: https://muhar.assay.guide/cli
