mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* fix(filesystem): close file handles only through the owned-handle owners Three sites still closed a fs/promises FileHandle in a finally block, where a failed close replaced the operation's own failure or result: the agentenv runner head read, the Linux cgroup kernel-file read and the JavaScript analysis transfer writer. They now use withOwnedFileHandle, which retains the operation outcome alongside a failed close. A lint rule, rea/no-raw-file-handle-close, refuses close() on a handle opened from fs/promises or declared as FileHandle, and fs close/closeSync on a descriptor, in src outside src/filesystem/Owned*. * fix(lint): distinguish owned handle class fields * fix(filesystem): retain diagnostic file cleanup owners * test(hopper): retry after confirmed application exit * fix(filesystem): settle doctor peers, sample cgroup outside admission, and bind lint this Desktop doctor waits for every client inspection before reporting a retained close failure. Ghidra memory cgroup sampling no longer passes the resource scope's admission gate, so a pending owner cannot mask the provider failure; failed closes are still retained for session cleanup. The raw FileHandle close rule resolves this through arrow functions only, treats static blocks as static, and keeps #name distinct from constructor parameter properties. * fix(ghidra): join in-flight cgroup samples before session cleanup ArtifactResourceScope.track runs optional work outside the admission gate while cleanup drains still join it, so a cancelled request's final retry cannot finish before a sample retains a failed close. * fix(filesystem): join work tracked during a cleanup drain and report doctor file cleanup as typed errors A cleanup retry now repeats until no tracked work is active and every retained owner has had one attempt, so diagnostic work admitted while an earlier owner is releasing is joined. Doctor reports a retained runner or configuration file close as a ProviderCleanupError bound to its lease instead of rethrowing a raw file failure, so the error document keeps the retained file. * fix(desktop): read the agentenv runner head through the artifact file owner The runner read retained a failed close under one lease while the reported failure carried another, so a successful cleanup retry left the doctor error reporting an already-closed file. withArtifactFileHandle binds one lease to both.
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect, it, vi } from "vitest";
|
|
|
|
import { ArtifactFileCleanupFailure } from "../artifacts/ArtifactFileOperation.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { OwnedFileHandle } from "../filesystem/OwnedFileHandle.js";
|
|
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
|
|
import { agentEnvInterpreter } from "./AgentEnvComputerHelper.js";
|
|
|
|
it("retains a failed runner descriptor close for retry by the caller", async () => {
|
|
const directory = await createTestTempDirectory("rea-agentenv-close-");
|
|
const path = join(directory, "agent-env");
|
|
await writeFile(path, "#!/usr/bin/python3\n");
|
|
const resources = new ArtifactResourceScope();
|
|
const close = vi
|
|
.spyOn(OwnedFileHandle.prototype, "close")
|
|
.mockRejectedValueOnce(new Error("close denied"));
|
|
try {
|
|
let failure: unknown;
|
|
try {
|
|
await agentEnvInterpreter(
|
|
{ REA_AGENT_ENV_COMMAND: path },
|
|
"desktop_build_doctor",
|
|
resources,
|
|
);
|
|
} catch (cause: unknown) {
|
|
failure = cause;
|
|
}
|
|
expect(failure).toBeInstanceOf(ArtifactFileCleanupFailure);
|
|
expect(resources.cleanupResources).toContain(path);
|
|
await resources.retryCleanup();
|
|
expect(resources.cleanupResources).toEqual([]);
|
|
// The reported failure shares the retained lease, so the retry resolves it.
|
|
expect(
|
|
failure instanceof ArtifactFileCleanupFailure &&
|
|
failure.cleanupLease.released,
|
|
).toBe(true);
|
|
close.mockRejectedValueOnce(new Error("close denied without scope"));
|
|
let unscoped: unknown;
|
|
try {
|
|
await agentEnvInterpreter(
|
|
{ REA_AGENT_ENV_COMMAND: path },
|
|
"check_build_task",
|
|
);
|
|
} catch (cause: unknown) {
|
|
unscoped = cause;
|
|
}
|
|
expect(unscoped).toBeInstanceOf(ArtifactFileCleanupFailure);
|
|
if (unscoped instanceof ArtifactFileCleanupFailure)
|
|
await unscoped.cleanupLease.release();
|
|
} finally {
|
|
close.mockRestore();
|
|
await resources.retryCleanup();
|
|
}
|
|
});
|