fix(ci): stabilize windows team runtime tests

This commit is contained in:
777genius 2026-04-25 00:56:30 +03:00
parent 2f37be4bd0
commit 465b031873
3 changed files with 41 additions and 25 deletions

View file

@ -24,13 +24,24 @@ export function getTeamLaunchSummaryPath(teamName: string): string {
return path.join(getTeamsBasePath(), teamName, TEAM_LAUNCH_SUMMARY_FILE);
}
async function isMissingTeamDirectoryWriteRace(teamName: string, error: unknown): Promise<boolean> {
async function isMissingTeamDirectoryWriteRace(
targetPath: string,
error: unknown
): Promise<boolean> {
const code = (error as NodeJS.ErrnoException).code;
if (code !== 'ENOENT' && code !== 'EINVAL') {
return false;
}
const targetDir = path.dirname(targetPath);
const errorPaths = [
(error as NodeJS.ErrnoException).path,
(error as NodeJS.ErrnoException & { dest?: string }).dest,
].filter((value): value is string => typeof value === 'string' && value.length > 0);
if (code === 'ENOENT' && errorPaths.some((errorPath) => path.dirname(errorPath) === targetDir)) {
return true;
}
try {
await fs.promises.access(path.dirname(getTeamLaunchStatePath(teamName)));
await fs.promises.access(targetDir);
return false;
} catch {
return true;
@ -53,17 +64,16 @@ export class TeamLaunchStateStore {
}
async write(teamName: string, snapshot: PersistedTeamLaunchSnapshot): Promise<void> {
const launchStatePath = getTeamLaunchStatePath(teamName);
const launchSummaryPath = getTeamLaunchSummaryPath(teamName);
try {
await atomicWriteAsync(launchStatePath, `${JSON.stringify(snapshot, null, 2)}\n`);
await atomicWriteAsync(
getTeamLaunchStatePath(teamName),
`${JSON.stringify(snapshot, null, 2)}\n`
);
await atomicWriteAsync(
getTeamLaunchSummaryPath(teamName),
launchSummaryPath,
`${JSON.stringify(createPersistedLaunchSummaryProjection(snapshot), null, 2)}\n`
);
} catch (error) {
if (await isMissingTeamDirectoryWriteRace(teamName, error)) {
if (await isMissingTeamDirectoryWriteRace(launchStatePath, error)) {
return;
}
logger.warn(

View file

@ -11962,8 +11962,8 @@ describe('Team agent launch matrix safe e2e', () => {
svc.stopTeam(teamName);
await waitForCondition(() => adapter.stopInputs.length === 2);
expect(staleKillCount).toBe(0);
expect(currentKillCount).toBe(1);
expectDirectChildKillCount(staleKillCount, 0);
expectDirectChildKillCount(currentKillCount, 1);
expect(staleRun.cancelRequested).toBe(false);
expect(currentRun.cancelRequested).toBe(true);
expect(adapter.stopInputs.map((input) => input.laneId).sort()).toEqual([
@ -12019,8 +12019,8 @@ describe('Team agent launch matrix safe e2e', () => {
await svc.cancelProvisioning(staleRun.runId);
expect(staleKillCount).toBe(1);
expect(currentKillCount).toBe(0);
expectDirectChildKillCount(staleKillCount, 1);
expectDirectChildKillCount(currentKillCount, 0);
expect(staleRun.cancelRequested).toBe(true);
expect(currentRun.cancelRequested).toBe(false);
expect(adapter.stopInputs).toEqual([]);
@ -12085,8 +12085,8 @@ describe('Team agent launch matrix safe e2e', () => {
svc.stopTeam(teamName);
expect(staleKillCount).toBe(0);
expect(currentKillCount).toBe(1);
expectDirectChildKillCount(staleKillCount, 0);
expectDirectChildKillCount(currentKillCount, 1);
expect(staleRun.cancelRequested).toBe(false);
expect(currentRun.cancelRequested).toBe(true);
expect(await svc.getRuntimeState(teamName)).toMatchObject({
@ -12120,8 +12120,8 @@ describe('Team agent launch matrix safe e2e', () => {
await svc.cancelProvisioning(staleRun.runId);
expect(staleKillCount).toBe(1);
expect(currentKillCount).toBe(0);
expectDirectChildKillCount(staleKillCount, 1);
expectDirectChildKillCount(currentKillCount, 0);
expect(staleRun.cancelRequested).toBe(true);
expect(currentRun.cancelRequested).toBe(false);
expect(svc.isTeamAlive(teamName)).toBe(true);
@ -12157,8 +12157,8 @@ describe('Team agent launch matrix safe e2e', () => {
await svc.cancelProvisioning(currentRun.runId);
expect(staleKillCount).toBe(0);
expect(currentKillCount).toBe(1);
expectDirectChildKillCount(staleKillCount, 0);
expectDirectChildKillCount(currentKillCount, 1);
expect(staleRun.cancelRequested).toBe(false);
expect(currentRun.cancelRequested).toBe(true);
expect(svc.isTeamAlive(teamName)).toBe(false);
@ -16465,6 +16465,11 @@ function trackLiveRun(svc: TeamProvisioningService, run: any): void {
(svc as any).aliveRunByTeam.set(run.teamName, run.runId);
}
function expectDirectChildKillCount(actual: number, expected: number): void {
// Windows uses taskkill.exe for process-tree termination, so fake child.kill is not called.
expect(actual).toBe(process.platform === 'win32' ? 0 : expected);
}
function injectStaleTerminalProvisioningRun(
svc: TeamProvisioningService,
teamName: string,

View file

@ -69,13 +69,14 @@ describe('electron userData migration', () => {
}
it('derives legacy candidates beside the current Electron userData directory', () => {
expect(
getLegacyElectronUserDataCandidates('/Users/me/Library/Application Support/Agent Teams UI')
).toEqual([
'/Users/me/Library/Application Support/Claude Agent Teams UI',
'/Users/me/Library/Application Support/claude-agent-teams-ui',
'/Users/me/Library/Application Support/claude-devtools',
'/Users/me/Library/Application Support/claude-code-context',
const currentPath = path.join('/Users/me/Library/Application Support', 'Agent Teams UI');
const parentPath = path.dirname(currentPath);
expect(getLegacyElectronUserDataCandidates(currentPath)).toEqual([
path.join(parentPath, 'Claude Agent Teams UI'),
path.join(parentPath, 'claude-agent-teams-ui'),
path.join(parentPath, 'claude-devtools'),
path.join(parentPath, 'claude-code-context'),
]);
});