From 6113fe9727ec220f369e4fcc47ad18bea5ca7537 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 2 Aug 2026 09:31:21 +0000 Subject: [PATCH] fix(browser): handled stackless cmux rejections Attributed unmatched rejection reasons when exactly one cmux guest run is active, covering primitive and library-created failures without guessing between concurrent runs. Updated the process probe to reject with a primitive value. Fixes #7365 --- .../src/tools/browser/cmux/cmux-tab.ts | 61 +++++++++++-------- .../browser-cmux-guest-rejection.test.ts | 2 +- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts b/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts index e315a1847..449d02577 100644 --- a/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts +++ b/packages/coding-agent/src/tools/browser/cmux/cmux-tab.ts @@ -270,36 +270,45 @@ const recentCmuxRunFiles = new Set(); function consumeCmuxRunRejection(reason: unknown): boolean { const stack = reason instanceof Error && typeof reason.stack === "string" ? reason.stack : undefined; - if (!stack) return false; + if (stack) { + let owner: ActiveCmuxRun | undefined; + let ownerIndex = -1; + for (const run of activeCmuxRuns.values()) { + const index = stack.lastIndexOf(run.filename); + if (index > ownerIndex) { + ownerIndex = index; + owner = run; + } + } + if (owner) { + owner.floatingRejections.push(reason); + return true; + } - let owner: ActiveCmuxRun | undefined; - let ownerIndex = -1; - for (const run of activeCmuxRuns.values()) { - const index = stack.lastIndexOf(run.filename); - if (index > ownerIndex) { - ownerIndex = index; - owner = run; + let recent: string | undefined; + let recentIndex = -1; + for (const filename of recentCmuxRunFiles) { + const index = stack.lastIndexOf(filename); + if (index > recentIndex) { + recentIndex = index; + recent = filename; + } + } + if (recent) { + logger.warn("Unhandled rejection from a finished cmux browser run (missing await?)", { + filename: recent, + error: reason, + }); + return true; } } - if (owner) { - owner.floatingRejections.push(reason); - return true; - } - let recent: string | undefined; - let recentIndex = -1; - for (const filename of recentCmuxRunFiles) { - const index = stack.lastIndexOf(filename); - if (index > recentIndex) { - recentIndex = index; - recent = filename; - } - } - if (!recent) return false; - logger.warn("Unhandled rejection from a finished cmux browser run (missing await?)", { - filename: recent, - error: reason, - }); + // Rejection interceptors receive no promise identity. With one live cmux run, + // it is the only guest owner available for stackless or library-created reasons. + if (activeCmuxRuns.size !== 1) return false; + const only = activeCmuxRuns.values().next().value; + if (!only) return false; + only.floatingRejections.push(reason); return true; } diff --git a/packages/coding-agent/test/tools/browser-cmux-guest-rejection.test.ts b/packages/coding-agent/test/tools/browser-cmux-guest-rejection.test.ts index 0f52973f8..4fff51b89 100644 --- a/packages/coding-agent/test/tools/browser-cmux-guest-rejection.test.ts +++ b/packages/coding-agent/test/tools/browser-cmux-guest-rejection.test.ts @@ -18,7 +18,7 @@ try { await runCmuxCode(tab, { code: \` const { promise, reject } = Promise.withResolvers(); - reject(new Error("boom")); + reject("boom"); await Bun.file("package.json").text(); await promise.catch(() => undefined); \`,