## New red on main
#2775 landed and put one failure on `main`, in a test that PR itself
added:
```
pr-create-review-lane-resolved.test.ts
> refuses WITHOUT naming a phantom lane when the workflow declares no review lane
AssertionError: expected undefined to be defined
```
## Two review rounds pushed `pr.ts` in opposite directions; the test is
from the losing one
| round | decision |
|---|---|
| **1** (greptile P2) | a resolved workflow with no review-trait column
is an **answer** — do not invent `'in-review'`, say *"no review lane"*.
**This test was written against that.** |
| **2** (greptile) | refusing on an empty set rejects **every v1
workflow**, because `synthesizeDefaultColumns` upgrades a v1 graph by
emitting every column with `traits: []` — so a v1 board whose
`in-review` column plainly exists resolves to an empty review set. |
**Round 2 shipped** (`pr.ts:206-207`) and is right: an empty set is
indistinguishable from a v1 upgrade, so it means *unexpressed* rather
than *absent* and takes the same legacy fallback as an unreadable
workflow. Both rounds are extensively documented in `pr.ts` — the code
is deliberate and I have not touched it.
The consequence is simply that **there is no "no review lane" message in
the shipped code at all**, so `errors.find((e) => e.includes("no review
lane"))` returned `undefined`. The test could never have passed against
what merged.
## The fix
Re-pointed at the contract that actually shipped: the filtered board
takes the legacy `'in-review'` fallback, and the refusal must **not**
name the renamed lanes (`signoff`, `waiting-on-a-human`) that this board
no longer declares — which preserves the anti-phantom-lane intent the
test was named for.
## Flagged, not guessed
The round-1 behaviour is **not recoverable** without a way to
distinguish *"v2 board that declares no review lane"* from *"v1 board
whose traits were synthesised empty"*. The IR does not currently carry
that signal, so emitting a distinct message would re-break every pre-v2
project — the exact regression round 2 caught. Recorded in the test
rather than invented.
## Evidence
Mutations, both caught:
| mutation | result |
|---|---|
| fallback names lanes the board lacks | **1 failed** |
| the review-lane gate removed entirely | **2 failed** |
Full CLI package **1684 passed / 106 skipped (126 files)** — was 1
failed. Gate **732 green** · lint clean. Test-only; `pr.ts` restored
clean after the mutations.
## How this was found
Pre-flighting the open batch PRs against current `main` rather than
their branch heads, after batch-engine's previous landing put 32
failures on main that were only caught post-merge. #2785 and #2783 both
came back clean (commented on each); re-running `main` itself after the
newest landings surfaced this one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>