Bug
If a reviewer pass is killed mid-run (e.g. the user closes/cancels the reviewer's terminal directly instead of letting it finish), retrying review for the same commit returns "Review is already up to date for this commit." even though no review actually completed.
Analyzed against: a8a3056
Confidence: High — traced exact code path
Root Cause
Engine.Trigger (backend/internal/review/review.go:189-193) decides idempotency purely from row existence + status, with no liveness check:
if existing, ok, err := e.store.GetReviewRunBySessionAndSHA(ctx, workerID, targetSHA); err != nil {
return TriggerResult{}, err
} else if ok && existing.Status != domain.ReviewRunFailed {
return TriggerResult{Run: existing, ReviewerHandleID: review.ReviewerHandleID, Created: false}, nil
}
Any non-Failed row for (session, targetSHA) — including one still Running — short-circuits with Created: false, which the frontend renders as "Review is already up to date" (frontend/src/renderer/components/SessionInspector.tsx:208).
Nothing transitions a Running row to Failed/Complete when the underlying reviewer process dies outside of Submit(). Submit (review.go:278-317) only fires when the reviewer agent itself reports a verdict. Killing the terminal bypasses that entirely, so the row stays Running forever. The only place that checks actual liveness (e.launcher.Alive, review.go:247) is in the "reuse a live pane" branch further down — which is never reached because the idempotency check above already returned.
Fix
In the idempotency check, when existing.Status == domain.ReviewRunRunning, call e.launcher.Alive(ctx, review.ReviewerHandleID) before short-circuiting. If the handle is dead, mark the run Failed (reuse the existing failRun pattern) and fall through to spawn a fresh review instead of returning the stale row.
Impact
A worker session can get permanently stuck unable to re-review a commit once a reviewer pass is killed mid-run, with no workaround short of pushing a new commit.
Bug
If a reviewer pass is killed mid-run (e.g. the user closes/cancels the reviewer's terminal directly instead of letting it finish), retrying review for the same commit returns "Review is already up to date for this commit." even though no review actually completed.
Analyzed against:
a8a3056Confidence: High — traced exact code path
Root Cause
Engine.Trigger(backend/internal/review/review.go:189-193) decides idempotency purely from row existence + status, with no liveness check:Any non-
Failedrow for(session, targetSHA)— including one stillRunning— short-circuits withCreated: false, which the frontend renders as "Review is already up to date" (frontend/src/renderer/components/SessionInspector.tsx:208).Nothing transitions a
Runningrow toFailed/Completewhen the underlying reviewer process dies outside ofSubmit().Submit(review.go:278-317) only fires when the reviewer agent itself reports a verdict. Killing the terminal bypasses that entirely, so the row staysRunningforever. The only place that checks actual liveness (e.launcher.Alive, review.go:247) is in the "reuse a live pane" branch further down — which is never reached because the idempotency check above already returned.Fix
In the idempotency check, when
existing.Status == domain.ReviewRunRunning, calle.launcher.Alive(ctx, review.ReviewerHandleID)before short-circuiting. If the handle is dead, mark the runFailed(reuse the existingfailRunpattern) and fall through to spawn a fresh review instead of returning the stale row.Impact
A worker session can get permanently stuck unable to re-review a commit once a reviewer pass is killed mid-run, with no workaround short of pushing a new commit.