Compare commits

...
Author SHA1 Message Date
Kaoru0822andClaude Opus 5.5 38998e465f fix(browser): keep cancellation for scenario captures cancelled during startup
capture_browser_scenario turned a cancellation into unreadable_output
whenever the retained capture failed the completed-report schema. A capture
cancelled while the initial navigation was pending retains no steps and no
completeness, which the partial-observation type deliberately allows, so the
caller lost both the typed cancellation and the retained observation. Only a
capture that reports completeness now claims to be a completed report; other
cancelled captures keep their cancellation and raw partial observation.

Fixes #5364

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:09:23 +00:00
2 changed files with 50 additions and 2 deletions

No files matched your search

@@ -1,6 +1,9 @@
import { expect, it } from "vitest";
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
import {
AnalysisCancelledError,
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import { BrowserObservationError } from "../domain/browserObservationError.js";
import { parseEvidence } from "../domain/evidence.js";
import { browserScenarioInputSchema } from "../domain/browserScenario.js";
@@ -77,6 +80,45 @@ it("converts a raw scenario capture retained after cleanup failure", async () =>
}
});
it("keeps cancellation and the incomplete capture retained before navigation finished", async () => {
const { completeness: _completeness, ...complete } = capture;
// The startup failure path retains no steps and no completeness.
const incomplete = { ...complete, steps: [] };
const failure = new AnalysisCancelledError("capture_browser_scenario", {
partialObservation: {
kind: "browser-scenario-observation",
capture: incomplete,
},
});
const provider: BrowserScenarioCapturePort = {
identity: () => ({ id: "fixture", name: "fixture", version: "1" }),
captureScenario: async () => err(failure),
};
const result = await captureBrowserScenario(provider, scenario);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.partialObservation).toEqual({
kind: "browser-scenario-observation",
capture: incomplete,
});
});
it("rejects a cancelled capture that claims completeness but is malformed", async () => {
const failure = new AnalysisCancelledError("capture_browser_scenario", {
partialObservation: {
kind: "browser-scenario-observation",
capture: { ...capture, steps: [] },
},
});
const provider: BrowserScenarioCapturePort = {
identity: () => ({ id: "fixture", name: "fixture", version: "1" }),
captureScenario: async () => err(failure),
};
const result = await captureBrowserScenario(provider, scenario);
if (result.ok) throw new Error("Expected failure");
expect(result.error).toBeInstanceOf(AnalysisOutputError);
});
it.each([
null,
{ request: "opaque cancellation" },
@@ -53,7 +53,13 @@ export const captureBrowserScenario = async (
provider.identity(),
);
} else if (!report.success) {
if (captured.error instanceof AnalysisCancelledError)
// Only a capture that reports completeness claims to be a completed
// report; one cancelled earlier, e.g. during initial navigation, is a
// legitimately incomplete observation and keeps its cancellation.
if (
captured.error instanceof AnalysisCancelledError &&
"completeness" in partial.capture
)
return err(
new AnalysisOutputError(
OPERATION,