fix(ci): preserve cleanup boundaries and bounded failure assertions (#1618)

Interrupt optional CDP domain shutdown on caller cancellation while preserving an independent bounded detach attempt. Retain original overflow and cleanup failures, verify workspace recovery, preserve user skill files, and reject another unverified firmware worker.

Validation: 149 integrated tests; repeated 64 affected boundary tests; full local check. Standalone CI includes real Chrome CLI/stdio MCP and real Web runtime checks.

Refs #1613
This commit is contained in:
Ysopwnable
2026-10-11 02:54:12 +08:00
committed by GitHub
parent b582b6ecb4
commit 16d9c5adef
5 changed files with 63 additions and 20 deletions
+6 -2
View File
@@ -51,18 +51,22 @@ export const closeCdpTargetSession = async (
): Promise<void> => {
const { connection, sessionId } = targetSession;
const cleanup = new AbortController();
const domainSignal =
signal === undefined
? cleanup.signal
: AbortSignal.any([cleanup.signal, signal]);
const timeout = setTimeout(() => cleanup.abort(), 1_000);
timeout.unref();
try {
if (signal?.aborted !== true) {
for (const domain of enabledDomains) {
if (cleanup.signal.aborted) break;
if (domainSignal.aborted) break;
try {
await connection.send(
`${domain}.disable`,
{},
sessionId,
cleanup.signal,
domainSignal,
);
} catch {
// Best-effort domain cleanup; transport close is the definitive boundary.
@@ -122,10 +122,8 @@ it("closes an Electron inspection session when cleanup is cancelled", async () =
operation: "inspect_electron_page",
},
});
expect(browser.commands.map(({ method }) => method)).toContain(
"Page.disable",
);
const methods = browser.commands.map(({ method }) => method);
expect(methods).not.toContain("Page.disable");
expect(methods).toContain("Target.detachFromTarget");
expect(methods).not.toContain("Target.closeTarget");
expect(methods).not.toContain("Browser.close");
@@ -7,6 +7,7 @@ import { projectAnalysisError } from "../../../src/domain/analysisErrorProjectio
import { evmInterfaceSchema } from "../../../src/domain/evm/evmInterface.js";
import { EvmoleInterfaceProvider } from "../../../src/evm/EvmoleInterfaceProvider.js";
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js";
import { waitForProviderProcessReady } from "../../fixtures/providerProcess.js";
import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js";
import {
@@ -128,6 +129,7 @@ it.runIf(!unsupportedHost)(
async () => {
const { path } = await fixture();
let ownedPath = "";
let failCleanup = true;
const provider = new EvmoleInterfaceProvider({}, async (spawn) => {
ownedPath = spawn.cwd ?? "";
const launched = await spawnOwnedProviderProcess({
@@ -135,24 +137,59 @@ it.runIf(!unsupportedHost)(
command: process.execPath,
arguments: ["-e", 'process.stdout.write("x".repeat(1048577))'],
});
const cleanupOwned =
launched.cleanup ??
(() => cleanupOwnedProcessGroup(launched.ownership));
return {
...launched,
cleanup: async () => {
await launched.cleanup?.();
throw new Error("injected post-cleanup reporting failure");
const cleaned = await cleanupOwned();
if (failCleanup)
throw new Error("injected post-cleanup reporting failure");
return cleaned;
},
};
});
onTestFinished(async () => {
failCleanup = false;
await provider.close();
});
const result = await provider.inspect({ path, encoding: "hex" });
if (result.ok) throw new Error("Expected compounded lifecycle failure");
expect(result.error).toMatchObject({
cleanupIncomplete: true,
diagnostics: {
reason: "injected post-cleanup reporting failure",
previous_error: { failure_kind: "output-limit" },
captured_output: { truncated: true, stderr: "" },
expect(result.error.cleanupIncomplete).toBe(true);
// Inspect the retained error chain without diffing a megabyte of stdout.
const output = z.object({ truncated: z.boolean(), stderr: z.string() });
const cleanup = z
.object({
reason: z.string(),
previous_error: z.object({
code: z.string(),
details: z.object({
diagnostics: z.object({
reason: z.string(),
previous_error: z.object({ failure_kind: z.string() }),
captured_output: output,
}),
}),
}),
})
.parse(projectAnalysisError(result.error).details?.["diagnostics"]);
expect(cleanup).toEqual({
reason: "Owned EVM worker cleanup could not be confirmed",
previous_error: {
code: "cleanup_incomplete",
details: {
diagnostics: {
reason: "injected post-cleanup reporting failure",
previous_error: { failure_kind: "output-limit" },
captured_output: { truncated: true, stderr: "" },
},
},
},
});
await access(ownedPath);
failCleanup = false;
await provider.close();
await expect(access(ownedPath)).rejects.toMatchObject({ code: "ENOENT" });
},
);
@@ -7,6 +7,7 @@ import { parse as parseJsonc } from "jsonc-parser";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js";
import { systemSetupHost } from "../../../src/application/SetupHost.js";
import { MANAGED_SKILL_FILES } from "../../../src/application/SetupSkill.js";
import { supportedClients } from "../../../src/application/SupportedClients.js";
import { systemUninstallHost } from "../../../src/application/Uninstall.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
@@ -151,6 +152,7 @@ describe("Qwen Code profile setup", () => {
});
expect(await setup.installSkill([client.name])).toBe("installed");
const skill = join(home, ".agents", "skills", PRODUCT_IDENTITY.skillName);
await writeFile(join(skill, "user-notes.txt"), "user-authored");
expect(await readFile(join(skill, "SKILL.md"), "utf8")).toContain(
"reverse-engineer-anything",
);
@@ -173,7 +175,13 @@ describe("Qwen Code profile setup", () => {
expect(registered).toEqual(client);
expect((await uninstall.removeClient(client)).status).toBe("removed");
expect((await uninstall.removeSkill()).status).toBe("removed");
await expect(access(skill)).rejects.toMatchObject({ code: "ENOENT" });
for (const relativePath of MANAGED_SKILL_FILES)
await expect(access(join(skill, relativePath))).rejects.toMatchObject({
code: "ENOENT",
});
expect(await readFile(join(skill, "user-notes.txt"), "utf8")).toBe(
"user-authored",
);
if (customHome)
await expect(access(join(home, ".qwen"))).rejects.toMatchObject({
code: "ENOENT",
@@ -324,7 +324,7 @@ it("cancels a waiting request promptly while keeping later launches behind the a
await assertFirmwareCleanup(fixture.launches);
});
it("retains an uncertain workspace and lets the next operation create a new root", async () => {
it("retains an uncertain worker and prevents another launch", async () => {
const fixture = await firmwareFixture("cleanup-failure");
const first = await fixture.service.execute("inspect_firmware_regions", {
path: fixture.path,
@@ -341,12 +341,8 @@ it("retains an uncertain workspace and lets the next operation create a new root
path: fixture.path,
});
expect(next).toMatchObject({ ok: false, error: { cleanupIncomplete: true } });
expect(fixture.launches).toHaveLength(2);
const nextWorkspace = fixture.launches[1]?.cwd;
expect(nextWorkspace).toBeDefined();
expect(nextWorkspace).not.toBe(workspace);
expect(fixture.launches).toHaveLength(1);
await access(workspace);
if (nextWorkspace !== undefined) await access(nextWorkspace);
});
it("accepts another build on the verified release line and reports it", async () => {