Eliminate recurring cross-platform CI flakes (#4171)

* fix(ci): eliminate recent cross-platform flakes

Synchronize tests on owned state instead of elapsed time or transient identities. Preserve file rename collision safety when platform file IDs are unavailable.

* fix(ci): close follow-up synchronization gaps

Keep fetches in flight through buffered-event classification, scope mock agents to navigation tests, and wait for live workspace hydration before opening agent routes.

* fix(ci): synchronize browser lifecycle boundaries
This commit is contained in:
Mohamed Boudra
2026-09-01 19:25:16 +02:00
committed by GitHub
parent 06fae60809
commit 59661f2bb8
13 changed files with 200 additions and 90 deletions
@@ -1,6 +1,7 @@
import {
appendFile,
chmod,
link,
mkdir,
mkdtemp,
readFile,
@@ -547,10 +548,14 @@ describe("file explorer service", () => {
try {
await writeFile(path.join(root, "source.txt"), "source", "utf8");
await writeFile(path.join(root, "existing.txt"), "existing", "utf8");
await link(path.join(root, "source.txt"), path.join(root, "source-link.txt"));
await expect(
renameExplorerEntry({ root, relativePath: "source.txt", name: "existing.txt" }),
).resolves.toEqual({ status: "error", error: '"existing.txt" already exists' });
await expect(
renameExplorerEntry({ root, relativePath: "source.txt", name: "source-link.txt" }),
).resolves.toEqual({ status: "error", error: '"source-link.txt" already exists' });
await expect(
renameExplorerEntry({ root, relativePath: "source.txt", name: "../outside.txt" }),
).resolves.toEqual({ status: "error", error: "Name cannot contain path separators" });
@@ -1,4 +1,4 @@
import { constants, promises as fs, type BigIntStats } from "fs";
import { constants, promises as fs, type BigIntStats, type Stats } from "fs";
import type { FileHandle } from "fs/promises";
import path from "path";
import { randomUUID } from "crypto";
@@ -707,11 +707,10 @@ export async function renameExplorerEntry({
}
throw error;
});
if (
targetStats &&
(targetStats.dev !== sourceStats.dev || targetStats.ino !== sourceStats.ino)
) {
return { status: "error", error: `"${trimmedName}" already exists` };
if (targetStats) {
if (!(await isCaseOnlyRename(source, targetPath, sourceStats, targetStats))) {
return { status: "error", error: `"${trimmedName}" already exists` };
}
}
const sourcePath = normalizeRelativePath({ root, targetPath: source.requestedPath });
@@ -782,6 +781,25 @@ function isEntryExistsError(error: unknown): boolean {
);
}
async function isCaseOnlyRename(
source: ScopedPath,
targetPath: string,
sourceStats: Stats,
targetStats: Stats,
): Promise<boolean> {
const hasStableFileIds =
sourceStats.dev !== 0 ||
sourceStats.ino !== 0 ||
targetStats.dev !== 0 ||
targetStats.ino !== 0;
const isSameEntry = hasStableFileIds
? targetStats.dev === sourceStats.dev && targetStats.ino === sourceStats.ino
: !sourceStats.isSymbolicLink() &&
!targetStats.isSymbolicLink() &&
(await fs.realpath(targetPath)) === source.resolvedPath;
return isSameEntry && source.requestedPath.toLowerCase() === targetPath.toLowerCase();
}
async function resolveScopedPath({
root,
relativePath = ".",
@@ -899,7 +899,9 @@ describe("WorkspaceGitService checkout observation", () => {
{ path: path.join(GIT_DIR, "refs", "remotes", "origin", "main"), type: "update" },
]);
releaseFetch.resolve();
await flushPromises();
await vi.waitFor(() => {
expect(service.getMetrics().fetchInFlightCount).toBe(0);
});
await vi.advanceTimersByTimeAsync(1_000);
await vi.waitFor(() => {
expect(getCheckoutSnapshotFacts).toHaveBeenCalledTimes(2);
@@ -3141,26 +3141,36 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
let result: WorkspaceGitFetchResult | null = null;
const eventsBeforeFetchSnapshot: FileChange[] = [];
try {
result = await this.deps.runGitFetch(
target.cwd,
{
onRefSnapshot: (phase) => {
const events = target.bufferedFetchMetadataEvents.splice(0);
if (phase === "before") {
eventsBeforeFetchSnapshot.push(...events);
}
try {
result = await this.deps.runGitFetch(
target.cwd,
{
onRefSnapshot: (phase) => {
const events = target.bufferedFetchMetadataEvents.splice(0);
if (phase === "before") {
eventsBeforeFetchSnapshot.push(...events);
}
},
},
},
createRunGitCommand("background-fetch"),
);
} catch (error) {
this.logger.warn(
{ err: error, repoGitRoot: target.repoGitRoot, cwd: target.cwd },
"Background git fetch failed",
);
createRunGitCommand("background-fetch"),
);
} catch (error) {
this.logger.warn(
{ err: error, repoGitRoot: target.repoGitRoot, cwd: target.cwd },
"Background git fetch failed",
);
}
this.applyRepoFetchResult(target, result, eventsBeforeFetchSnapshot);
} finally {
target.fetchInFlight = false;
}
}
private applyRepoFetchResult(
target: RepoGitTarget,
result: WorkspaceGitFetchResult | null,
eventsBeforeFetchSnapshot: FileChange[],
): void {
this.flushFetchMetadataEvents(target, eventsBeforeFetchSnapshot);
if (!result || result.changes === null) {
target.recentFetchRemoteRefChanges.clear();