fix(coding-agent): kept unrelated worker failures fatal

Classified only marked browser failures and evaluated-run stack frames as run-owned rejections. Unrelated tab-worker failures now remain on the worker guard's fatal path.
This commit is contained in:
brymko
2026-08-02 16:49:52 +08:00
parent 5c0187dec5
commit a51b69ee85
4 changed files with 25 additions and 9 deletions
@@ -10,7 +10,7 @@ import { resolveToCwd } from "../../path-utils";
import { formatScreenshot } from "../../render-utils";
import {
bindRunFacade,
isBrowserRunRejection,
isBrowserRunOwnedRejection,
markBrowserRunRejection,
observeBrowserRunPromise,
resolvePredicateTimeout,
@@ -1409,9 +1409,7 @@ export async function runCmuxCode(tab: CmuxTab, opts: RunCmuxCodeOptions): Promi
rejectFloatingFailure(error);
};
const uninstallRejectionInterceptor = postmortem.interceptUnhandledRejections(reason => {
const browserRunRejection = isBrowserRunRejection(reason, rejectionOwner);
const stack = reason instanceof Error && typeof reason.stack === "string" ? reason.stack : undefined;
if (!browserRunRejection && stack?.includes(`cmux-run-${runId}.js`) !== true) return false;
if (!isBrowserRunOwnedRejection(reason, rejectionOwner, `cmux-run-${runId}.js`)) return false;
recordFloatingFailure(reason);
return true;
});
@@ -24,7 +24,7 @@ import {
bindRunFacade,
CELL_BUDGET_SLACK_MS,
installBrowserWorkerRejectionGuard,
isBrowserRunRejection,
isBrowserRunOwnedRejection,
markBrowserRunRejection,
markHandled,
observeBrowserRunPromise,
@@ -784,10 +784,7 @@ export class WorkerCore {
#consumeUnhandledRejection(reason: unknown): boolean {
const active = this.#active;
if (!active) return false;
const browserRunRejection = isBrowserRunRejection(reason, active.rejectionOwner);
const stack = reason instanceof Error && typeof reason.stack === "string" ? reason.stack : undefined;
const fromRun = stack?.includes(`browser-run-${active.id}.js`) === true;
if (!browserRunRejection && !this.#isolated && !fromRun) return false;
if (!isBrowserRunOwnedRejection(reason, active.rejectionOwner, `browser-run-${active.id}.js`)) return false;
this.#recordFloatingRejection(active, reason);
return true;
}
@@ -22,6 +22,12 @@ export function isBrowserRunRejection(reason: unknown, owner: object): boolean {
);
}
/** Returns whether a rejection belongs to the marked browser run or its evaluated source file. */
export function isBrowserRunOwnedRejection(reason: unknown, owner: object, filename: string): boolean {
if (isBrowserRunRejection(reason, owner)) return true;
return reason instanceof Error && typeof reason.stack === "string" && reason.stack.includes(filename);
}
type FloatingRejectionHandler = (reason: unknown) => void;
interface ObservedPromiseState {
@@ -4,6 +4,7 @@ import { postmortem } from "@oh-my-pi/pi-utils";
import { JsRuntime, type RuntimeHooks } from "../../src/eval/js/shared/runtime";
import {
bindRunFacade,
isBrowserRunOwnedRejection,
isBrowserRunRejection,
markBrowserRunRejection,
markHandled,
@@ -147,6 +148,20 @@ describe("browser run cancellation", () => {
expect(isBrowserRunRejection(new Error("unrelated", { cause: browserFailure }), owner)).toBe(false);
});
it("keeps unrelated worker rejections outside the active browser run", () => {
const owner = {};
const workerFailure = new Error("transport failed");
workerFailure.stack = "Error: transport failed\n at tab-worker.ts:1:1";
const evaluatedFailure = new Error("evaluated failure");
evaluatedFailure.stack = "Error: evaluated failure\n at browser-run-run-1.js:1:1";
expect(isBrowserRunOwnedRejection(workerFailure, owner, "browser-run-run-1.js")).toBe(false);
expect(isBrowserRunOwnedRejection(evaluatedFailure, owner, "browser-run-run-1.js")).toBe(true);
expect(
isBrowserRunOwnedRejection(markBrowserRunRejection(workerFailure, owner), owner, "browser-run-run-1.js"),
).toBe(true);
});
it("keeps a later cause-wrapped rejection on the fatal path", async () => {
vi.useRealTimers();
const script = `