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); \`,