fix(ci): stabilize windows mcp preflight cleanup
This commit is contained in:
parent
cd4e9ccba8
commit
d766d174e3
2 changed files with 77 additions and 11 deletions
|
|
@ -205,6 +205,9 @@ const TEAM_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,127}$/;
|
|||
const RUN_TIMEOUT_MS = 300_000;
|
||||
const VERIFY_TIMEOUT_MS = 15_000;
|
||||
const VERIFY_POLL_MS = 500;
|
||||
const MCP_PREFLIGHT_SHUTDOWN_GRACE_MS = 250;
|
||||
const MCP_PREFLIGHT_SHUTDOWN_TIMEOUT_MS = 2_000;
|
||||
const MCP_PREFLIGHT_SHUTDOWN_POLL_MS = 50;
|
||||
const STDERR_RING_LIMIT = 64 * 1024;
|
||||
const STDOUT_RING_LIMIT = 64 * 1024;
|
||||
// Progress emissions fan out the latest CLI tail + assistant output to the
|
||||
|
|
@ -1004,6 +1007,39 @@ async function waitForPidsToExit(
|
|||
}
|
||||
}
|
||||
|
||||
async function waitForChildProcessToExit(
|
||||
child: ChildProcess | null | undefined,
|
||||
timeoutMs: number
|
||||
): Promise<void> {
|
||||
if (!child?.pid || !isProcessAlive(child.pid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
let settled = false;
|
||||
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const finish = (): void => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
child.off('close', finish);
|
||||
child.off('exit', finish);
|
||||
child.off('error', finish);
|
||||
resolve();
|
||||
};
|
||||
|
||||
timeoutHandle = setTimeout(finish, timeoutMs);
|
||||
child.once('close', finish);
|
||||
child.once('exit', finish);
|
||||
child.once('error', finish);
|
||||
});
|
||||
}
|
||||
|
||||
async function tryReadRegularFileUtf8(
|
||||
filePath: string,
|
||||
opts: { timeoutMs: number; maxBytes: number }
|
||||
|
|
@ -13588,11 +13624,26 @@ export class TeamProvisioningService {
|
|||
throw new Error(this.buildAgentTeamsMcpValidationError(errorText));
|
||||
} finally {
|
||||
rejectAll(new Error('agent-teams MCP preflight session closed'));
|
||||
if (child?.stdin && !child.stdin.destroyed) {
|
||||
child.stdin.end();
|
||||
if (child?.stdin && !child.stdin.destroyed && !child.stdin.writableEnded) {
|
||||
const stdin = child.stdin;
|
||||
await new Promise<void>((resolve) => {
|
||||
try {
|
||||
stdin.end(() => resolve());
|
||||
} catch {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (child) {
|
||||
killProcessTree(child);
|
||||
if (child?.pid) {
|
||||
await waitForChildProcessToExit(child, MCP_PREFLIGHT_SHUTDOWN_GRACE_MS);
|
||||
if (isProcessAlive(child.pid)) {
|
||||
killProcessTree(child);
|
||||
await waitForPidsToExit([child.pid], {
|
||||
timeoutMs: MCP_PREFLIGHT_SHUTDOWN_TIMEOUT_MS,
|
||||
pollMs: MCP_PREFLIGHT_SHUTDOWN_POLL_MS,
|
||||
});
|
||||
await waitForChildProcessToExit(child, MCP_PREFLIGHT_SHUTDOWN_GRACE_MS);
|
||||
}
|
||||
}
|
||||
await fs.promises.rm(fixture.claudeDir, { recursive: true, force: true }).catch(() => {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,26 @@ process.stdin.on('data', (chunk) => {
|
|||
return scriptPath;
|
||||
}
|
||||
|
||||
async function removeTempRoot(dirPath: string): Promise<void> {
|
||||
if (!dirPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxAttempts = process.platform === 'win32' ? 20 : 1;
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
try {
|
||||
await fs.promises.rm(dirPath, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
if ((code !== 'EBUSY' && code !== 'EPERM') || attempt === maxAttempts) {
|
||||
throw error;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||
let tempRoot = '';
|
||||
|
||||
|
|
@ -166,13 +186,8 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
|||
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempRoot, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 5,
|
||||
retryDelay: 200,
|
||||
});
|
||||
afterEach(async () => {
|
||||
await removeTempRoot(tempRoot);
|
||||
});
|
||||
|
||||
it('does not create missing directories during prepareForProvisioning', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue