fix(agent-teams): address race condition in team deletion locking

This commit is contained in:
Nguyen Khac Trung Kien 2026-02-08 13:10:26 +07:00 committed by YeonGyu-Kim
parent c15bad6d00
commit 0f0ba0f71b
2 changed files with 35 additions and 9 deletions

View File

@ -4,7 +4,7 @@ import { mkdtempSync, rmSync } from "node:fs"
import { tmpdir } from "node:os" import { tmpdir } from "node:os"
import { join } from "node:path" import { join } from "node:path"
import { acquireLock } from "../../features/claude-tasks/storage" import { acquireLock } from "../../features/claude-tasks/storage"
import { getTeamDir } from "./paths" import { getTeamDir, getTeamTaskDir } from "./paths"
import { createTeamConfig, deleteTeamData, teamExists } from "./team-config-store" import { createTeamConfig, deleteTeamData, teamExists } from "./team-config-store"
describe("agent-teams team config store", () => { describe("agent-teams team config store", () => {
@ -45,4 +45,27 @@ describe("agent-teams team config store", () => {
//#then //#then
expect(teamExists("core")).toBe(false) expect(teamExists("core")).toBe(false)
}) })
test("deleteTeamData waits for task lock before removing task files", () => {
//#given
const lock = acquireLock(getTeamTaskDir("core"))
expect(lock.acquired).toBe(true)
try {
//#when
const deleteWhileLocked = () => deleteTeamData("core")
//#then
expect(deleteWhileLocked).toThrow("team_task_lock_unavailable")
expect(teamExists("core")).toBe(true)
} finally {
lock.release()
}
//#when
deleteTeamData("core")
//#then
expect(teamExists("core")).toBe(false)
})
}) })

View File

@ -23,6 +23,7 @@ import {
isTeammateMember, isTeammateMember,
} from "./types" } from "./types"
import { validateTeamName } from "./name-validation" import { validateTeamName } from "./name-validation"
import { withTeamTaskLock } from "./team-task-store"
function nowMs(): number { function nowMs(): number {
return Date.now() return Date.now()
@ -174,15 +175,17 @@ export function assignNextColor(config: TeamConfig): string {
export function deleteTeamData(teamName: string): void { export function deleteTeamData(teamName: string): void {
assertValidTeamName(teamName) assertValidTeamName(teamName)
withTeamLock(teamName, () => { withTeamLock(teamName, () => {
const teamDir = getTeamDir(teamName) withTeamTaskLock(teamName, () => {
const taskDir = getTeamTaskDir(teamName) const teamDir = getTeamDir(teamName)
const taskDir = getTeamTaskDir(teamName)
if (existsSync(teamDir)) { if (existsSync(teamDir)) {
rmSync(teamDir, { recursive: true, force: true }) rmSync(teamDir, { recursive: true, force: true })
} }
if (existsSync(taskDir)) { if (existsSync(taskDir)) {
rmSync(taskDir, { recursive: true, force: true }) rmSync(taskDir, { recursive: true, force: true })
} }
})
}) })
} }