Context
Raised by an operator asking whether the six-prong review had been getting skipped. Measured across every .pi/work-state/*.json in this repo: it had not — every cycle that reached lens-review ran lens-review×6, and no cycle recorded a lens-skipped-empty-diff event. Cycles where the review appears absent (#277, #279, #305, #308) aborted or handed off before reaching the step.
But the question exposed a real hazard, and it is the same class as #380's ci-status: substring: a gate whose "no signal" answer is approval.
runLens (work-driver-lens.ts:98-104) treats an empty diff as approved:
if (!diff.trim()) {
next = appendEvent(next,
{ kind: "lens-skipped-empty-diff", ... },
{ kind: "lens-approved", ... }); // <-- approves
return next;
}
And the diff it tests comes from fetchIntegratedDiff (work-driver-diff.ts:131-149), which swallows every error and returns "":
} catch (err) {
trace(`work-driver: fetchIntegratedDiff(${branchName}) failed: ...`);
return "";
}
So a transient git failure, a stale origin/<branch> ref, a maxBuffer overrun on a large diff, or an unset branchName all produce the same value as "there is genuinely nothing to review" — and that value means approve and proceed to merge. Under always-worktree (#287) the fallback path (fetchMergedDiff from a worktree detached at baseSha) is also empty, so there is no second opinion.
This has bitten twice already from the other direction: pre-PR11 the guard fired on every successful cycle (34ms skip → code merged unreviewed), and #287's comment records the same failure being re-introduced when worktrees went detached. Both were fixed by changing which diff is read. The structural problem — that an unreadable diff is indistinguishable from an empty one, and both approve — was never fixed.
What to build
A. Distinguish "empty" from "unreadable." fetchIntegratedDiff must report failure rather than returning "". A discriminated result ({ok: true, diff} / {ok: false, reason}) or a thrown error the caller handles — either is fine; what matters is that the caller can tell the two apart.
B. An unreadable diff must NOT approve. Emit a cap-hit (lens-diff-unreadable or similar) routing to handoff, with the git error in the message. Halting on an unreadable diff is cheap; merging unreviewed code is not.
C. A genuinely empty diff should still be justified. Confirm it positively — e.g. git rev-list --count origin/<base>..origin/<branch> is 0 — rather than inferring it from the absence of output. Same shape as #380's evidence gate: prove the green, do not assume it.
D. Make the skip visible. lens-skipped-empty-diff currently only lands in the event log. When a cycle merges without a six-pass review, that fact belongs in the PR body and the merged event, not only in a JSON file nobody opens.
Acceptance criteria
Out of scope
Changing which diff is read (that is settled by #287) or the six lenses themselves.
This work must ship as its own separate PR, independent of any other open issue.
Context
Raised by an operator asking whether the six-prong review had been getting skipped. Measured across every
.pi/work-state/*.jsonin this repo: it had not — every cycle that reachedlens-reviewranlens-review×6, and no cycle recorded alens-skipped-empty-diffevent. Cycles where the review appears absent (#277, #279, #305, #308) aborted or handed off before reaching the step.But the question exposed a real hazard, and it is the same class as #380's
ci-status:substring: a gate whose "no signal" answer is approval.runLens(work-driver-lens.ts:98-104) treats an empty diff as approved:And the diff it tests comes from
fetchIntegratedDiff(work-driver-diff.ts:131-149), which swallows every error and returns"":So a transient git failure, a stale
origin/<branch>ref, amaxBufferoverrun on a large diff, or an unsetbranchNameall produce the same value as "there is genuinely nothing to review" — and that value means approve and proceed to merge. Under always-worktree (#287) the fallback path (fetchMergedDifffrom a worktree detached at baseSha) is also empty, so there is no second opinion.This has bitten twice already from the other direction: pre-PR11 the guard fired on every successful cycle (34ms skip → code merged unreviewed), and #287's comment records the same failure being re-introduced when worktrees went detached. Both were fixed by changing which diff is read. The structural problem — that an unreadable diff is indistinguishable from an empty one, and both approve — was never fixed.
What to build
A. Distinguish "empty" from "unreadable."
fetchIntegratedDiffmust report failure rather than returning"". A discriminated result ({ok: true, diff}/{ok: false, reason}) or a thrown error the caller handles — either is fine; what matters is that the caller can tell the two apart.B. An unreadable diff must NOT approve. Emit a cap-hit (
lens-diff-unreadableor similar) routing to handoff, with the git error in the message. Halting on an unreadable diff is cheap; merging unreviewed code is not.C. A genuinely empty diff should still be justified. Confirm it positively — e.g.
git rev-list --count origin/<base>..origin/<branch>is 0 — rather than inferring it from the absence of output. Same shape as #380's evidence gate: prove the green, do not assume it.D. Make the skip visible.
lens-skipped-empty-diffcurrently only lands in the event log. When a cycle merges without a six-pass review, that fact belongs in the PR body and the merged event, not only in a JSON file nobody opens.Acceptance criteria
lens-approvedevents.maxBufferdoes not silently approve."").Out of scope
Changing which diff is read (that is settled by #287) or the six lenses themselves.
This work must ship as its own separate PR, independent of any other open issue.