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); 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; const code = (error as NodeJS.ErrnoException).code;
if (code !== 'ENOENT' && code !== 'EINVAL') { if (code !== 'ENOENT' && code !== 'EINVAL') {
return false; 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 { try {
await fs.promises.access(path.dirname(getTeamLaunchStatePath(teamName))); await fs.promises.access(targetDir);
return false; return false;
} catch { } catch {
return true; return true;
@ -53,17 +64,16 @@ export class TeamLaunchStateStore {
} }
async write(teamName: string, snapshot: PersistedTeamLaunchSnapshot): Promise<void> { async write(teamName: string, snapshot: PersistedTeamLaunchSnapshot): Promise<void> {
const launchStatePath = getTeamLaunchStatePath(teamName);
const launchSummaryPath = getTeamLaunchSummaryPath(teamName);
try { try {
await atomicWriteAsync(launchStatePath, `${JSON.stringify(snapshot, null, 2)}\n`);
await atomicWriteAsync( await atomicWriteAsync(
getTeamLaunchStatePath(teamName), launchSummaryPath,
`${JSON.stringify(snapshot, null, 2)}\n`
);
await atomicWriteAsync(
getTeamLaunchSummaryPath(teamName),
`${JSON.stringify(createPersistedLaunchSummaryProjection(snapshot), null, 2)}\n` `${JSON.stringify(createPersistedLaunchSummaryProjection(snapshot), null, 2)}\n`
); );
} catch (error) { } catch (error) {
if (await isMissingTeamDirectoryWriteRace(teamName, error)) { if (await isMissingTeamDirectoryWriteRace(launchStatePath, error)) {
return; return;
} }
logger.warn( logger.warn(

View file

@ -11962,8 +11962,8 @@ describe('Team agent launch matrix safe e2e', () => {
svc.stopTeam(teamName); svc.stopTeam(teamName);
await waitForCondition(() => adapter.stopInputs.length === 2); await waitForCondition(() => adapter.stopInputs.length === 2);
expect(staleKillCount).toBe(0); expectDirectChildKillCount(staleKillCount, 0);
expect(currentKillCount).toBe(1); expectDirectChildKillCount(currentKillCount, 1);
expect(staleRun.cancelRequested).toBe(false); expect(staleRun.cancelRequested).toBe(false);
expect(currentRun.cancelRequested).toBe(true); expect(currentRun.cancelRequested).toBe(true);
expect(adapter.stopInputs.map((input) => input.laneId).sort()).toEqual([ 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); await svc.cancelProvisioning(staleRun.runId);
expect(staleKillCount).toBe(1); expectDirectChildKillCount(staleKillCount, 1);
expect(currentKillCount).toBe(0); expectDirectChildKillCount(currentKillCount, 0);
expect(staleRun.cancelRequested).toBe(true); expect(staleRun.cancelRequested).toBe(true);
expect(currentRun.cancelRequested).toBe(false); expect(currentRun.cancelRequested).toBe(false);
expect(adapter.stopInputs).toEqual([]); expect(adapter.stopInputs).toEqual([]);
@ -12085,8 +12085,8 @@ describe('Team agent launch matrix safe e2e', () => {
svc.stopTeam(teamName); svc.stopTeam(teamName);
expect(staleKillCount).toBe(0); expectDirectChildKillCount(staleKillCount, 0);
expect(currentKillCount).toBe(1); expectDirectChildKillCount(currentKillCount, 1);
expect(staleRun.cancelRequested).toBe(false); expect(staleRun.cancelRequested).toBe(false);
expect(currentRun.cancelRequested).toBe(true); expect(currentRun.cancelRequested).toBe(true);
expect(await svc.getRuntimeState(teamName)).toMatchObject({ expect(await svc.getRuntimeState(teamName)).toMatchObject({
@ -12120,8 +12120,8 @@ describe('Team agent launch matrix safe e2e', () => {
await svc.cancelProvisioning(staleRun.runId); await svc.cancelProvisioning(staleRun.runId);
expect(staleKillCount).toBe(1); expectDirectChildKillCount(staleKillCount, 1);
expect(currentKillCount).toBe(0); expectDirectChildKillCount(currentKillCount, 0);
expect(staleRun.cancelRequested).toBe(true); expect(staleRun.cancelRequested).toBe(true);
expect(currentRun.cancelRequested).toBe(false); expect(currentRun.cancelRequested).toBe(false);
expect(svc.isTeamAlive(teamName)).toBe(true); expect(svc.isTeamAlive(teamName)).toBe(true);
@ -12157,8 +12157,8 @@ describe('Team agent launch matrix safe e2e', () => {
await svc.cancelProvisioning(currentRun.runId); await svc.cancelProvisioning(currentRun.runId);
expect(staleKillCount).toBe(0); expectDirectChildKillCount(staleKillCount, 0);
expect(currentKillCount).toBe(1); expectDirectChildKillCount(currentKillCount, 1);
expect(staleRun.cancelRequested).toBe(false); expect(staleRun.cancelRequested).toBe(false);
expect(currentRun.cancelRequested).toBe(true); expect(currentRun.cancelRequested).toBe(true);
expect(svc.isTeamAlive(teamName)).toBe(false); 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); (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( function injectStaleTerminalProvisioningRun(
svc: TeamProvisioningService, svc: TeamProvisioningService,
teamName: string, teamName: string,

View file

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