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)
|
const statuses = Object.fromEntries(statusEntries)
|
||||||
|
|
||||||
// Batch self-heal stale in_review flags — reload fresh state to avoid overwriting concurrent changes
|
// Self-heal stale in_review flags — re-check activity to avoid racing with a concurrent review start
|
||||||
if (staleReviewSessions.length > 0) {
|
for (const s of staleReviewSessions) {
|
||||||
for (const s of staleReviewSessions) s.in_review = false
|
const stillActive = await vm.isClaudeActive(s.worktree, s.branch)
|
||||||
const fresh = await state.load(root).catch(() => null)
|
if (!stillActive) {
|
||||||
if (fresh) {
|
await state.patchSession(root, s.branch, { in_review: false }).catch(() => {})
|
||||||
for (const s of staleReviewSessions) {
|
|
||||||
if (fresh.sessions[s.branch]) fresh.sessions[s.branch].in_review = false
|
|
||||||
}
|
|
||||||
await state.save(root, fresh).catch(() => {})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,14 +68,12 @@ Your thoughts, in brief.
|
||||||
`
|
`
|
||||||
if (extra) prompt += "\n\n" + extra
|
if (extra) prompt += "\n\n" + extra
|
||||||
|
|
||||||
session.in_review = true
|
await state.patchSession(root, session.branch, { in_review: true })
|
||||||
await state.setSession(root, session)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (opts.print) {
|
if (opts.print) {
|
||||||
spin.text = "Running review…"
|
spin.text = "Running review…"
|
||||||
const result = await vm.claude(session.worktree, { print: prompt })
|
const result = await vm.claude(session.worktree, { print: prompt })
|
||||||
spin.stop()
|
|
||||||
if (result.output) process.stdout.write(result.output + "\n")
|
if (result.output) process.stdout.write(result.output + "\n")
|
||||||
} else {
|
} else {
|
||||||
spin.succeed("Session ready")
|
spin.succeed("Session ready")
|
||||||
|
|
@ -83,13 +81,9 @@ Your thoughts, in brief.
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
spin.stop()
|
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
|
// Save worktree changes only in interactive mode
|
||||||
if (!opts.print) await saveChanges(session.worktree, session.branch).catch(() => {})
|
if (!opts.print) await saveChanges(session.worktree, session.branch)
|
||||||
// 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(() => {})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,14 @@ export async function setSession(repoRoot: string, session: Session): Promise<vo
|
||||||
await save(repoRoot, state)
|
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> {
|
export async function removeSession(repoRoot: string, branch: string): Promise<void> {
|
||||||
const state = await load(repoRoot)
|
const state = await load(repoRoot)
|
||||||
delete state.sessions[branch]
|
delete state.sessions[branch]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user