fix(sdk-ts): observe rejected operations after pre-abort
why
abortable(promise, signal) checks an already-aborted signal before attaching a handler to the supplied promise. The operation has already started by the time it is passed in. If it rejects immediately or later, the caller can correctly catch the cancellation error and still have Node terminate on the unhandled underlying rejection.
Minimal repository reproduction (node --unhandled-rejections=strict --input-type=module):
import { abortable } from './packages/sdk-ts/src/abort.ts';
const controller = new AbortController();
controller.abort(new Error('caller canceled'));
try {
await abortable(Promise.reject(new Error('transport closed')), controller.signal);
} catch (error) {
console.log(error.message); // caller canceled
}
await new Promise(resolve => setTimeout(resolve, 20));
// Before: Node exits with the unhandled "transport closed" error.
The same failure was reproduced through the SDK's internal resolveBrowserWebSocketUrl consumer with an injected fetch that aborts its signal and rejects. No browser, external service, or credentials are needed for that reproduction.
what changed
- Observe the supplied promise's rejection before returning the existing cancellation error on the pre-aborted path.
- Keep the original cancellation reason and normal/running-operation behavior.
- Add strict Node subprocess regressions for immediately rejected and later-rejecting operations. Each child catches the cancellation and must exit successfully.
- Add a TypeScript SDK patch Changeset. No protocol or Python behavior changes.
test plan
Base: b7a4b899079f198a0ee78a6dcb62ad02f545cf49.
- Before the fix: 2 new subprocess regressions failed; 4 existing abort tests passed. Both helper and CDP-discovery reproductions exited 1 after the cancellation was caught.
- After: all 6 abort tests passed; the CDP-discovery reproduction preserves the original reason and exits 0.
- Full SDK unit run: 621 passed, 1 failed. The remaining
packageContract.test.tsconsumer-typecheck failure was reproduced on the unchanged same base: both versions produce the identical 208 TypeScript dependency diagnostics (duplicate Node types and incompatible undici declarations). No full-suite pass is claimed. - SDK typecheck, touched-file oxlint/oxfmt, and git diff check passed.
- Extension production build and complete SDK build passed, including publint.
- Changeset, consolidated changelog, and Python version metadata checks passed.
- Real-browser and Python suites were not run for this TypeScript-only cancellation helper change.
AI assistance was used to investigate, implement, and validate this patch.
Summary by cubic
Fixes abortable so an already-aborted signal no longer leaves the underlying promise's rejection unhandled, which could terminate Node in strict mode after the caller caught the cancellation error.
- Observes the supplied promise's rejection before returning the original cancellation reason.
- Adds strict-mode subprocess regressions for immediate and deferred rejections.
- Adds a patch Changeset for
@browserbasehq/stagehand; no protocol or Python changes.