From 2f524da29284f77380fea46e5ab13ed19919ea43 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 18 Mar 2026 23:55:49 -0700 Subject: [PATCH] Fix stale review flag healing and prevent concurrent state overwrites Use already-loaded state in list command instead of re-reading. In review command, patch in_review on fresh state to avoid clobbering concurrent changes, and skip worktree save in print mode. Remove unused white import and unnecessary nullish coalescing fallback. Co-Authored-By: Claude Opus 4.6 --- src/commands/list.ts | 12 +++--------- src/commands/review.ts | 13 +++++++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/commands/list.ts b/src/commands/list.ts index 28f1039..f2743d1 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -2,7 +2,7 @@ import { homedir } from "os" import * as git from "../git.ts" import * as vm from "../vm.ts" import * as state from "../state.ts" -import { reset, dim, bold, green, yellow, cyan, magenta, white, red } from "../fmt.ts" +import { reset, dim, bold, green, yellow, cyan, magenta, red } from "../fmt.ts" export async function action(opts: { json?: boolean }) { const root = await git.repoRoot() @@ -62,13 +62,7 @@ export async function action(opts: { json?: boolean }) { // Batch self-heal stale in_review flags with a single state write if (staleReviewSessions.length > 0) { for (const s of staleReviewSessions) s.in_review = false - const current = await state.load(root).catch(() => null) - if (current) { - for (const s of staleReviewSessions) { - if (current.sessions[s.branch]) current.sessions[s.branch].in_review = false - } - await state.save(root, current).catch(() => {}) - } + await state.save(root, st).catch(() => {}) } if (opts.json) { @@ -93,7 +87,7 @@ export async function action(opts: { json?: boolean }) { for (const s of sessions) { const prompt = (s.prompt ?? "").split("\n")[0] const status = statuses[s.branch] ?? "idle" - const { icon, color: bc } = styles[status] ?? styles.idle + const { icon, color: bc } = styles[status] const maxPrompt = cols - prefixWidth const truncated = maxPrompt > 3 && prompt.length > maxPrompt ? prompt.slice(0, maxPrompt - 3) + "..." : prompt console.log(`${icon} ${bc}${s.branch.padEnd(branchWidth)}${reset} ${dim}${truncated}${reset}`) diff --git a/src/commands/review.ts b/src/commands/review.ts index 7face00..5e0b4f5 100644 --- a/src/commands/review.ts +++ b/src/commands/review.ts @@ -82,9 +82,14 @@ Your thoughts, in brief. await vm.claude(session.worktree, { prompt }) } } finally { - // Always attempt save and clear review state - await saveChanges(session.worktree, session.branch).catch(() => {}) - session.in_review = false - await state.setSession(root, session).catch(() => {}) + spin.stop() + // Save worktree changes only in interactive mode + if (!opts.print) await saveChanges(session.worktree, session.branch).catch(() => {}) + // Patch only in_review on fresh state to avoid overwriting concurrent changes + const current = await state.load(root).catch(() => null) + if (current?.sessions[session.branch]) { + current.sessions[session.branch].in_review = false + await state.save(root, current).catch(() => {}) + } } }