# Output and exit codes

muhar's stdout is a report meant to be read by people and parsed by scripts. Its format is fixed: everything on this page is taken from the code that prints it. The suite's own output goes to stderr ([Streams](cli.md#streams)).

## A healthy run

```console
$ muhar -spec mutations.json
Harness healthy: baseline GREEN, positive control CAUGHT.
  CAUGHT           zero is accepted as an amount
  CAUGHT           the limit ignores what was already spent today
  NOT_CAUGHT       a withdrawal that lands exactly on the limit is refused
  COULD_NOT_MUTATE stale anchor  (mutation "stale anchor": Old text not found in limits.go — edit did not land)
Totals: 2 caught, 1 NOT CAUGHT, 1 could-not-mutate.
NOTE: could-not-mutate is NOT a clean gate — those guards were never actually tested.
$ echo $?
0
```

| Line | Format | Present |
|---|---|---|
| Health | `Harness healthy: baseline GREEN, positive control CAUGHT.` | Always, first. |
| Verdict | Two spaces, the verdict left-aligned in a 16-character field, one space, the mutation's `name`. A `COULD_NOT_MUTATE` line adds two spaces and the reason in parentheses. | One per mutation, in spec order. |
| Totals | `Totals: <c> caught, <n> NOT CAUGHT, <m> could-not-mutate.` | Always, after the verdicts. |
| Note | `NOTE: could-not-mutate is NOT a clean gate — those guards were never actually tested.` | Only when `<m>` is above 0. |

The verdict words are `CAUGHT`, `NOT_CAUGHT` and `COULD_NOT_MUTATE`, with underscores. The `Totals:` line spells the middle count `NOT CAUGHT`, with a space; grep for exactly what is printed. The reasons are listed in the [spec reference](spec.md#cnm-reasons).

With `-shard`, the counts cover the shard's own mutations only.

## A broken run

```console
$ muhar -spec mutations.json
HARNESS BROKEN — run discarded.
  baseline suite is RED on pristine source — cannot distinguish a caught mutation from a pre-existing failure; discard the run
$ echo $?
2
```

A broken run prints two lines: `HARNESS BROKEN — run discarded.` and the reason, indented two spaces. It prints no verdicts and no `Totals:` line, because those verdicts are exactly what cannot be trusted. A gate that looks for a `Totals:` line will therefore never pass a broken run, even if it ignores the exit status.

| Reason | Cause |
|---|---|
| `baseline suite is RED on pristine source — cannot distinguish a caught mutation from a pre-existing failure; discard the run` | The suite failed before anything was mutated. Fix the suite or the `test` command first. |
| `positive control could not be planted (<reason>) — the harness is not mutating; discard the run` | The control's edit could not be made or proven. Usually its `old` text has drifted. |
| `positive control was NOT CAUGHT — a mutation the suite must catch stayed green, so the harness is broken (not mutating, or the suite cannot see the source); discard the run` | The suite passed with the control in place. The control is too weak, the suite does not build the mutated file, or `root` points at a different tree. |
| `could not provision isolated workspace <i> (<error>) — a parallel sweep cannot share a tree; discard the run` | With `-j` above 1, a worker's copy of the tree could not be made: disk full, unreadable file, or a `go.mod` rewrite that failed. |

## Exit codes

| Status | Meaning | Report on stdout |
|---|---|---|
| `0` | Harness healthy. Verdicts are trustworthy. This includes runs with `NOT_CAUGHT` or `COULD_NOT_MUTATE` lines. | Full report with `Totals:`. |
| `1` | Usage, spec or setup error: missing `-spec`, unreadable or invalid spec, missing `test` or `control`, bad `-shard`, negative `-j`, temp directory not creatable. | None. Message on stderr. |
| `2` | `HARNESS BROKEN`: no verdict from this run may be used. Also returned by Go's flag parser for an unknown flag or a malformed flag value such as `-j two`. | `HARNESS BROKEN` and reason, or nothing for a flag error. |
| `130` | Interrupted by `SIGINT` or `SIGTERM`. | None. |

Exit 0 is not a pass. It says the instrument worked. Whether the guards held is in the `Totals:` line.

Exit 2 is never a pass. A broken harness reports nothing about your tests, and reading its silence as "no missing tests" is the failure muhar exists to prevent. The flag parser sharing status 2 is a known collision, tracked as [assay#2644](https://github.com/medici-finance/assay/issues/2644); it does not change the rule below.

## Gating on the result

The rule: fail on any non-zero exit status, then require a `Totals:` line with zero `NOT CAUGHT` and zero `could-not-mutate`. In bash:

```bash
status=0
report=$(muhar -spec mutations.json) || status=$?
printf '%s\n' "$report"
if [ "$status" -ne 0 ]; then
  echo "muhar exited $status: harness broken or usage error, no verdict" >&2
  exit 1
fi
if ! grep -qE '^Totals: [0-9]+ caught, 0 NOT CAUGHT, 0 could-not-mutate\.$' <<<"$report"; then
  echo "a guard has no test, or a mutation did not land" >&2
  exit 1
fi
```

Against the runs in the [worked example](getting-started.md), this script passes only the run after the boundary test is added. It fails the `NOT_CAUGHT` run, the `COULD_NOT_MUTATE` run and all three broken runs.

## Parsing the report

For scripts and agents, every verdict line matches:

```text
^  (CAUGHT|NOT_CAUGHT|COULD_NOT_MUTATE) +(.+)$
```

The name runs to the end of the line, except on a `COULD_NOT_MUTATE` line, where the reason follows two spaces and an opening parenthesis: `  (mutation "<name>": …)`. The name is free text, so keep names free of the sequence two-spaces-parenthesis to keep the split unambiguous. The report has no machine-readable format other than this text today.

---

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