When removing Ghidra's private runtime directory failed, close_binary reported only a generic `provider-client` resource, and a later close, after the permission was restored, returned success while the directory and its contents remained. The client dropped its reference to the root before removing it, and PrivateRuntimeRoot cached the rejected removal. Keep each runtime or socket root until its removal succeeds, report a failure as a ProviderCleanupError naming the leftover paths, let PrivateRuntimeRoot retry a failed removal, and pass such typed cleanup failures through closeAnalysisClient instead of replacing them. Fixes #1190 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33 lines
1.1 KiB
TypeScript
33 lines
1.1 KiB
TypeScript
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
|
import { err, ok, type Result } from "../../domain/result.js";
|
|
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
|
import type { AnalysisClient, ExecutionOptions } from "../AnalysisProvider.js";
|
|
|
|
/** Close one provider client and normalize unexpected adapter rejection. */
|
|
export const closeAnalysisClient = async (
|
|
client: AnalysisClient,
|
|
providerId: string,
|
|
options: Pick<ExecutionOptions, "progress"> & {
|
|
readonly retainDocument?: boolean;
|
|
} = {},
|
|
): Promise<Result<null, AnalysisError>> => {
|
|
try {
|
|
if (client.closeWithOutcome !== undefined)
|
|
return await client.closeWithOutcome(options);
|
|
await client.close();
|
|
return ok(null);
|
|
} catch (cause: unknown) {
|
|
// A typed cleanup failure already names its leftover resources.
|
|
if (cause instanceof AnalysisError && cause.cleanupIncomplete)
|
|
return err(cause);
|
|
return err(
|
|
new ProviderCleanupError(
|
|
providerId,
|
|
["provider-client"],
|
|
{ reason: "provider client close rejected unexpectedly" },
|
|
{ cause },
|
|
),
|
|
);
|
|
}
|
|
};
|