You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
waitFor's second parameter is now an options object — waitFor(method, { predicate?, timeoutMs? }) — and the positional (method, predicate?, timeoutMs?) form is removed. The skill's navigation examples register the load waiter before Page.navigate so a fast load event can't be missed.
Both real bugs from #110 are fixed here; the difference is how much API we own afterwards.
The evidence that settled the signature: in observed agent traces, a model that wanted a navigation timeout wrote waitFor("Page.loadEventFired", { timeoutMs: 15000 }).catch(() => {}) — under the positional signature the object became the predicate, the predicate throw was swallowed, the waiter ran to the default 30s, and the defensive .catch masked the timeout. A silent flat 30s tax on every navigation. The options object is the shape models assume from priors (Puppeteer/Playwright), so we make the one invented signature in session.ts match the prior instead of asking models to learn ours.
Given that, the overload machinery in #111 (positional + options forms, argument-shape validation ladder) isn't needed — there is exactly one call shape, and the only guard is a synchronous TypeError when a function is passed where options belong, so stragglers fail loud at the call site instead of stalling silently. Sync-throw also can't be swallowed by a .catch on the returned promise.
Snippet-side void loaded.catch(() => {}) incantation — waitFor now pre-observes its own promise, so an abandoned waiter times out without an unhandled rejection. Fixed once in the session layer instead of taught in the skill forever.
loaderId / same-document branching in the skill examples — the canonical example stays the 3-line happy path; Page.navigate's errorText behavior is noted in one line (that's real CDP knowledge worth teaching, the branching isn't).
navigate() wrapper — session.ts stays a transport shim; navigation composition belongs to the snippet, guided by the skill.
Predicate-throw → reject (with unsubscribe) is kept from #111, as is the mock-WS test structure (credited in the test file).
Validation
bun test test/cdp-session.test.ts — 4 pass
bun test (package) — 16 pass, 8 env-gated skips, 1 pre-existing unrelated failure (edit + cache-bust workspace import; fails identically on main)
bun typecheck from packages/bcode-browser — clean
Summary by cubic
Make waitFor options-only and race-safe. Prevents silent 30s stalls and ensures navigation waiters catch fast load events.
Bug Fixes
waitFor(method, { predicate?, timeoutMs? }) is the only supported form; passing a function as the second arg or a positional third-arg timeout now throws synchronously.
Abandoned waiters no longer create unhandled rejections; predicate errors reject immediately and unsubscribe.
Examples/tests now register the load waiter before Page.navigate to avoid missing a fast Page.loadEventFired.
Migration
Replace waitFor(method, predicate?, timeoutMs?) with waitFor(method, { predicate, timeoutMs }).
Register const loaded = session.waitFor("Page.loadEventFired", { timeoutMs }) before Page.navigate({ url }), then await loaded.
Remove waitFor(...).catch(() => {}) that was only used to silence unhandled rejections.
Written for commit c15d706. Summary will update on new commits.
c15d706 — closes the last silent 30s path. The existing guard only caught waitFor(m, fn, ...) (function in the options slot). It did not catch the other positional shape, waitFor(m, undefined, 10_000), which quietly dropped the third argument and reverted to the 30s default — verified against this branch: that call was still pending after 600ms while { timeoutMs: 60 } rejected at 61ms. Since the entire point of the PR is removing a silent 30s stall, leaving one open defeated it. Now an extra argument is a ...rest: never[] — a compile error for TS callers and a synchronous TypeError for runtime-written snippets. Test added covering the positional-timeout throw plus the supported form actually honouring its timeout.
a427292 — merged origin/main. This branch was 925 commits behind, cut before the v1.18.4 sync (#116) and rebrand (#117). Clean merge, no conflicts. Worth knowing: on the old base the filtered root typecheck failed on @opencode-ai/effect-drizzle-sqlite (examples/basic.ts(28,26) Effect Defect/Top drift) — pre-existing on that base, fixed in main, so the pre-push gate blocked until the merge. Now 16/16 tasks pass.
Package suite: 17 pass, 8 env-gated skips, 1 pre-existing unrelated edit + cache-bust failure that reproduces on bare main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces #111. Fixes #110.
waitFor's second parameter is now an options object —waitFor(method, { predicate?, timeoutMs? })— and the positional(method, predicate?, timeoutMs?)form is removed. The skill's navigation examples register the load waiter beforePage.navigateso a fast load event can't be missed.Why this shape instead of #111
Both real bugs from #110 are fixed here; the difference is how much API we own afterwards.
The evidence that settled the signature: in observed agent traces, a model that wanted a navigation timeout wrote
waitFor("Page.loadEventFired", { timeoutMs: 15000 }).catch(() => {})— under the positional signature the object became the predicate, the predicate throw was swallowed, the waiter ran to the default 30s, and the defensive.catchmasked the timeout. A silent flat 30s tax on every navigation. The options object is the shape models assume from priors (Puppeteer/Playwright), so we make the one invented signature insession.tsmatch the prior instead of asking models to learn ours.Given that, the overload machinery in #111 (positional + options forms, argument-shape validation ladder) isn't needed — there is exactly one call shape, and the only guard is a synchronous
TypeErrorwhen a function is passed where options belong, so stragglers fail loud at the call site instead of stalling silently. Sync-throw also can't be swallowed by a.catchon the returned promise.Also intentionally not carried over from #111:
void loaded.catch(() => {})incantation —waitFornow pre-observes its own promise, so an abandoned waiter times out without an unhandled rejection. Fixed once in the session layer instead of taught in the skill forever.loaderId/ same-document branching in the skill examples — the canonical example stays the 3-line happy path;Page.navigate'serrorTextbehavior is noted in one line (that's real CDP knowledge worth teaching, the branching isn't).waitFor— real question, but it's a semantics change beyond fix(browser): make waitFor options explicit and race-safe #110's scope; should be its own decision if traces show it biting.navigate()wrapper —session.tsstays a transport shim; navigation composition belongs to the snippet, guided by the skill.Predicate-throw → reject (with unsubscribe) is kept from #111, as is the mock-WS test structure (credited in the test file).
Validation
bun test test/cdp-session.test.ts— 4 passbun test(package) — 16 pass, 8 env-gated skips, 1 pre-existing unrelated failure (edit + cache-bustworkspace import; fails identically onmain)bun typecheckfrompackages/bcode-browser— cleanSummary by cubic
Make
waitForoptions-only and race-safe. Prevents silent 30s stalls and ensures navigation waiters catch fast load events.Bug Fixes
waitFor(method, { predicate?, timeoutMs? })is the only supported form; passing a function as the second arg or a positional third-arg timeout now throws synchronously.Page.navigateto avoid missing a fastPage.loadEventFired.Migration
waitFor(method, predicate?, timeoutMs?)withwaitFor(method, { predicate, timeoutMs }).const loaded = session.waitFor("Page.loadEventFired", { timeoutMs })beforePage.navigate({ url }), thenawait loaded.waitFor(...).catch(() => {})that was only used to silence unhandled rejections.Written for commit c15d706. Summary will update on new commits.