fix(ci): bound manual git fetches (#87839)

* fix(ci): bound manual git fetches

* fix(ci): cover platform fetch guards

* fix(ci): fail timed out target fetches

* fix(ci): repair typecheck regressions

* fix(ci): refresh CI expectations

* fix(ci): preserve main cron coverage
This commit is contained in:
Dallin Romney
2026-05-28 22:56:54 -07:00
committed by GitHub
parent 2e042fbca8
commit ed36f423da
9 changed files with 242 additions and 27 deletions

View File

@@ -10,21 +10,92 @@ describe("ci workflow guards", () => {
it("kills timed manual checkout fetches after the grace period", () => {
const workflowPaths = [
".github/workflows/ci.yml",
".github/workflows/workflow-sanity.yml",
".github/workflows/ci-check-testbox.yml",
".github/workflows/ci-build-artifacts-testbox.yml",
".github/workflows/crabbox-hydrate.yml",
];
for (const workflowPath of workflowPaths) {
const workflow = readFileSync(workflowPath, "utf8");
const fetchTimeouts = workflow.match(/timeout --signal=TERM[^\n]* 30s git -C "\$workdir"/g);
const fetchTimeouts = workflow.match(
/timeout --signal=TERM[^\n]* 30s git(?: -C "(?:\$workdir|\$GITHUB_WORKSPACE|clawhub-source)")?/g,
);
expect(fetchTimeouts?.length, workflowPath).toBeGreaterThan(0);
expect(fetchTimeouts, workflowPath).toEqual(
fetchTimeouts?.map(() => 'timeout --signal=TERM --kill-after=10s 30s git -C "$workdir"'),
expect(
fetchTimeouts?.every((line) =>
line.startsWith("timeout --signal=TERM --kill-after=10s 30s git"),
),
workflowPath,
).toBe(true);
}
});
it("bounds shared base commit fetches", () => {
const action = readFileSync(".github/actions/ensure-base-commit/action.yml", "utf8");
expect(action).toContain("fetch_base_ref()");
expect(action).toContain("timeout --signal=TERM --kill-after=10s 30s git");
expect(action).toContain("-c protocol.version=2");
expect(action).not.toContain("if ! git fetch --no-tags");
});
it("bounds early unauthenticated checkout fetches", () => {
const workflow = readCiWorkflow();
for (const jobName of ["preflight", "security-fast", "skills-python"]) {
const checkoutStep = workflow.jobs[jobName].steps.find((step) => step.name === "Checkout");
expect(checkoutStep.run, jobName).toContain(
'timeout --signal=TERM --kill-after=10s 30s git -C "$GITHUB_WORKSPACE"',
);
expect(checkoutStep.run, jobName).toContain("-c protocol.version=2");
expect(checkoutStep.run, jobName).toContain(
"fetch --no-tags --prune --no-recurse-submodules --depth=1 origin",
);
if (jobName !== "skills-python") {
expect(checkoutStep.run, jobName).toContain('if [ "$fetch_status" = "124" ]');
expect(checkoutStep.run, jobName).toContain("timed out");
}
expect(checkoutStep.run, jobName).not.toContain(
'git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1',
);
}
});
it("bounds platform checkout fetches without GNU timeout", () => {
const workflow = readCiWorkflow();
for (const jobName of ["checks-windows", "macos-node", "macos-swift"]) {
const checkoutStep = workflow.jobs[jobName].steps.find((step) => step.name === "Checkout");
expect(checkoutStep.run, jobName).toContain("fetch_checkout_ref()");
expect(checkoutStep.run, jobName).toContain("-c protocol.version=2");
expect(checkoutStep.run, jobName).toContain(
"fetch --no-tags --prune --no-recurse-submodules --depth=1 origin",
);
expect(checkoutStep.run, jobName).toContain('kill -TERM "$fetch_pid"');
expect(checkoutStep.run, jobName).toContain('kill -KILL "$fetch_pid"');
expect(checkoutStep.run, jobName).not.toContain(
'git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1',
);
}
});
it("bounds the Windows Crabbox hydrate main fetch", () => {
const workflow = readFileSync(".github/workflows/crabbox-hydrate.yml", "utf8");
expect(workflow).toContain("$fetch = Start-Process git");
expect(workflow).toContain('"protocol.version=2"');
expect(workflow).toContain('"--no-recurse-submodules"');
expect(workflow).toContain("$fetch.WaitForExit(30000)");
expect(workflow).toContain('throw "git fetch timed out after 30 seconds"');
expect(workflow).not.toContain(
'git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"',
);
});
it("runs dependency policy guards in PR CI preflight", () => {
const workflow = readFileSync(".github/workflows/ci.yml", "utf8");
const preflightGuards = workflow.slice(

View File

@@ -141,7 +141,13 @@ describe("package acceptance workflow", () => {
expect(hydratePnpm.run).toContain('corepack enable --install-directory "$PNPM_HOME"');
expect(hydratePnpm.run).toContain("COREPACK_HOME");
expect(workflowStep(hydrate, "Fetch main ref").run).toContain(
'git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"',
"timeout --signal=TERM --kill-after=10s 30s git",
);
expect(workflowStep(hydrate, "Fetch main ref").run).toContain(
"fetch --no-tags --prune --no-recurse-submodules --depth=50 origin",
);
expect(workflowStep(hydrate, "Fetch main ref").run).toContain(
'"+refs/heads/main:refs/remotes/origin/main"',
);
expect(workflowStep(hydrate, "Prepare Crabbox shell").if).toBeUndefined();
expect(workflowStep(hydrate, "Ensure Docker is running").if).toBeUndefined();
@@ -178,9 +184,11 @@ describe("package acceptance workflow", () => {
);
const hydrateWindowsFetch = workflowStep(hydrateWindowsDaemon, "Fetch main ref");
expect(hydrateWindowsFetch.shell).toBe("powershell");
expect(hydrateWindowsFetch.run).toContain(
'git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"',
);
expect(hydrateWindowsFetch.run).toContain("Start-Process git");
expect(hydrateWindowsFetch.run).toContain("WaitForExit(30000)");
expect(hydrateWindowsFetch.run).toContain('"fetch"');
expect(hydrateWindowsFetch.run).toContain('"--depth=50"');
expect(hydrateWindowsFetch.run).toContain('"+refs/heads/main:refs/remotes/origin/main"');
expect(workflowStep(hydrateWindowsDaemon, "Mark Crabbox ready").shell).toBe("powershell");
expect(workflowStep(hydrateWindowsDaemon, "Mark Crabbox ready").run).toContain('"NODE_BIN"');
expect(workflowStep(hydrateWindowsDaemon, "Mark Crabbox ready").run).toContain('"PNPM_HOME"');