# bugfix mut-diag-double-code — doubled `[code]` prefix in mut-local human diagnostics

**Date:** 2026-05-15
**Status:** DONE
**Trigger:** fieldtest finding F2 (`docs/specs/2026-05-15-fieldtest-mut-local.md`).
**Started from:** 1faee67

## Symptom

Non-JSON `ail check` stderr rendered the kebab diagnostic code
twice for the four mut-local `CheckError` variants, e.g.

```
error: [mut-var-captured-by-lambda] main: [mut-var-captured-by-lambda] mut-var `x` cannot be captured...
```

## Cause

`MutAssignOutOfScope`, `AssignTypeMismatch`,
`UnsupportedMutVarType`, `MutVarCapturedByLambda` were authored in
iters mut.2 / mut.4-tidy with their `#[error("[<code>] ...")]`
`thiserror` Display bodies embedding the bracketed code. The
non-JSON `Cmd::Check` formatter in `crates/ail/src/main.rs`
independently prepends `[{d.code}]` from `CheckError::code()`.
Embedded + prepended = doubled. The non-mut `CheckError` variants
never embedded the bracket, so only these four doubled — they were
the inconsistent ones, brought in line here.

## Fix

RED-first via the `debug` skill (`ailang-debugger` wrote the
failing test; the GREEN side was a trivial four-string mechanical
edit applied inline by the Boss per the CLAUDE.md
trivial-mechanical-edit carve-out — no review-and-commit
discipline shed).

- RED test: `crates/ail/tests/ct1_check_cli.rs` —
  `check_human_mode_renders_mut_diagnostic_code_exactly_once`.
  Pins the *observable* property: the code appears exactly once in
  the rendered human diagnostic, exercising the same formatter path
  the CLI uses (not a brittle assertion on the raw `#[error]` body).
- GREEN: dropped the leading `[<code>] ` literal from the four
  `#[error(...)]` Display strings in `crates/ailang-check/src/lib.rs`.

The mut-typecheck pin tests (`mut_typecheck_pin.rs`) and the
in-source unit assertions match on the struct variants / `code()`,
not the Display string, so the prefix removal did not touch them.

## Tests

598 → 599 green (the new RED-now-GREEN human-formatter test).
Full workspace clean, zero failures.

## Not in scope (stays queued)

F1/F4 (accumulator-over-iteration: the milestone motivation is
unmet without a loop construct) and F3 (zero-arg `(app f)` rejected
at parse) from the same fieldtest are NOT addressed here — they are
design/roadmap items, not this bug.
