fix(browser): classify export-web-scripts capture selection failures by cause

export_web_scripts reported a missing capture, a directory selected as
the capture, and an unreadable capture as an artifact io failure ("Script
export failed for <capture> -> <output>: ENOENT ..."), unlike its sibling
inspect_web_network_capture, which already reports these as invalid
input or access_denied.

Read the selected capture in its own step: ENOENT, ENOTDIR and EISDIR
become an invalid capture_path issue, and EACCES or EPERM becomes
access_denied. Failures while writing the export keep their existing
classification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 committed 2026-10-09 09:16:59 +00:00
1 parent f4c99ab8a5
commit 8c5e34cd37
2 files changed
+99 -7

No files matched your search

+38 -3
View File
@@ -8,6 +8,7 @@ import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { publishWebScripts } from "../browser/assets/PublishWebScripts.js";
import { selectScriptCapture } from "../browser/assets/ScriptCaptureAdapters.js";
import {
AnalysisAccessDeniedError,
AnalysisCancelledError,
AnalysisInputError,
AnalysisOutputError,
@@ -60,9 +61,9 @@ export const exportWebScriptsValidated = async (
);
try {
options.signal?.throwIfAborted();
const bytes = await readFile(input.capture_path, {
signal: options.signal,
});
const read = await readCapture(input.capture_path, options.signal);
if (!read.ok) return read;
const bytes = read.value;
const loaded = parseCapture(bytes);
if (!loaded.ok) return loaded;
options.signal?.throwIfAborted();
@@ -142,6 +143,40 @@ export const exportWebScriptsValidated = async (
}
};
/**
* Read the caller-selected capture. A missing, non-file or unreadable
* selection is a caller or host-permission failure, not an export failure.
*/
const readCapture = async (
path: string,
signal: AbortSignal | undefined,
): Promise<Result<Buffer, AnalysisError>> => {
try {
return ok(await readFile(path, { signal }));
} catch (cause: unknown) {
const code =
cause instanceof Error && "code" in cause ? String(cause.code) : "";
if (code === "EACCES" || code === "EPERM")
return err(
new AnalysisAccessDeniedError(OPERATION, path, code, { cause }),
);
if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR")
return err(
new AnalysisInputError(OPERATION, { cause }, [
{
path: ["capture_path"],
reason: "invalid_value",
message:
code === "EISDIR"
? `Selected capture is a directory, not a file: ${path}`
: `Selected capture could not be read (${code}): ${path}`,
},
]),
);
throw cause;
}
};
const parseCapture = (
bytes: Buffer,
): Result<ReturnType<typeof selectScriptCapture>, AnalysisError> => {