Use patchSession to avoid race conditions in review flag updates
The load-modify-save pattern could overwrite concurrent state changes. patchSession does an atomic read-patch-write, and the list command now re-checks activity before clearing stale flags to avoid racing with a review that just started. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ad90c9dcc1
commit
69ba73b3c3
|
|
@ -59,15 +59,11 @@ export async function action(opts: { json?: boolean }) {
|
|||
)
|
||||
const statuses = Object.fromEntries(statusEntries)
|
||||
|
||||
// Batch self-heal stale in_review flags — reload fresh state to avoid overwriting concurrent changes
|
||||
if (staleReviewSessions.length > 0) {
|
||||
for (const s of staleReviewSessions) s.in_review = false
|
||||
const fresh = await state.load(root).catch(() => null)
|
||||
if (fresh) {
|
||||
for (const s of staleReviewSessions) {
|
||||
if (fresh.sessions[s.branch]) fresh.sessions[s.branch].in_review = false
|
||||
}
|
||||
await state.save(root, fresh).catch(() => {})
|
||||
// Self-heal stale in_review flags — re-check activity to avoid racing with a concurrent review start
|
||||
for (const s of staleReviewSessions) {
|
||||
const stillActive = await vm.isClaudeActive(s.worktree, s.branch)
|
||||
if (!stillActive) {
|
||||
await state.patchSession(root, s.branch, { in_review: false }).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,14 +68,12 @@ Your thoughts, in brief.
|
|||
`
|
||||
if (extra) prompt += "\n\n" + extra
|
||||
|
||||
session.in_review = true
|
||||
await state.setSession(root, session)
|
||||
await state.patchSession(root, session.branch, { in_review: true })
|
||||
|
||||
try {
|
||||
if (opts.print) {
|
||||
spin.text = "Running review…"
|
||||
const result = await vm.claude(session.worktree, { print: prompt })
|
||||
spin.stop()
|
||||
if (result.output) process.stdout.write(result.output + "\n")
|
||||
} else {
|
||||
spin.succeed("Session ready")
|
||||
|
|
@ -83,13 +81,9 @@ Your thoughts, in brief.
|
|||
}
|
||||
} finally {
|
||||
spin.stop()
|
||||
// Clear review flag before saveChanges to minimize the race window
|
||||
await state.patchSession(root, session.branch, { in_review: false }).catch(() => {})
|
||||
// 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(() => {})
|
||||
}
|
||||
if (!opts.print) await saveChanges(session.worktree, session.branch)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,14 @@ export async function setSession(repoRoot: string, session: Session): Promise<vo
|
|||
await save(repoRoot, state)
|
||||
}
|
||||
|
||||
export async function patchSession(repoRoot: string, branch: string, patch: Partial<Session>): Promise<void> {
|
||||
const state = await load(repoRoot)
|
||||
if (state.sessions[branch]) {
|
||||
Object.assign(state.sessions[branch], patch)
|
||||
await save(repoRoot, state)
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeSession(repoRoot: string, branch: string): Promise<void> {
|
||||
const state = await load(repoRoot)
|
||||
delete state.sessions[branch]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user