fix(eve): close facade browser resources
why
This is PR 2 of the facade-close stack and depends on #2788.
The shared facade now propagates browser.close() to a host lifecycle callback, but Eve owns additional state that core cannot clean up generically: a keep-alive Browserbase session, its persisted session ID, reconnect/recovery decisions, and the currently cached facade tools. Eve must consume the signal at that ownership boundary.
Before this change, model-authored await browser.close() could return successfully while Eve retained the same facade and persisted Browserbase session. A later call could reuse authentication/cookie state from a browser the agent believed it had closed.
what changed
- Eve detaches the current facade before cleanup and serializes cleanup with later resource creation.
- Explicit close attempts both Stagehand and browser cleanup, then uses the shared Browserbase SDK release helper.
- Persisted ownership is cleared only after release succeeds; an ambiguous or failed release is marked suspect and surfaced.
- A later tool call creates fresh resources instead of reusing the closed facade.
- A model-code error does not poison the operation queue or discard a healthy browser.
- Initialization cleanup captures Browserbase release before
Stagehand.create(); if local close fails, release is attempted immediately and persisted ownership is either cleared or retained for retry. - Failed explicit release uses the shared typed
BrowserbaseSessionReleaseError. - Focused tests cover successful close/fresh creation, failed release reporting, local browser-close failure, persisted-ID clearing, and initialization cleanup.
behavior before and after
| Eve lifecycle | Before | After |
|---|---|---|
await browser.close() | Returns while retaining Eve-owned resources. | Awaits Eve-owned cleanup and release. |
| Browserbase state | Session can remain RUNNING. | Release is requested and verified through the shared helper. |
| Persisted session ID | Remains eligible for reconnect. | Cleared only after successful release. |
| Failed release | Can be masked by a successful tool result. | Surfaced and retained as suspect ownership. |
| Next call | Reuses the same facade/browser. | Creates fresh resources. |
| Browser close + release success | Local failure can be hidden. | Local cleanup failure is still surfaced. |
end-to-end evidence
An exact-base live A/B probe navigated to example.com, called await browser.close(), then requested facade tools again:
| Lifecycle result | Current main | Stack through this PR |
|---|---|---|
| Close call returned | Yes | Yes |
| Next call reused the same facade | Yes | No |
| Remote session after close | RUNNING | COMPLETED |
The follow-up call on the changed implementation created a fresh session and closed it; both changed-side sessions reached COMPLETED. The intentionally leaked base session was released after recording the result.
The same eight public Online-Mind2Web-style prompts were run through the official Eve example on real Browserbase sessions. Each required navigation, multiple facade calls, snapshot/screenshot evidence, exact final title/URL, and browser close:
| Result | Before | After |
|---|---|---|
| Manually correct task outcome | 8/8 | 8/8 |
| LLM-judge pass | 7/8 | 7/8 |
| All seven strict harness gates | 2/8 | 5/8 |
| Operationally clean trace | 2/8 | 6/8 |
| Browserbase cleanup | 0/8 sessions | 9/9 sessions |
Nine sessions were created after the change because one unhealthy resource was replaced; all nine reached COMPLETED. The unchanged judge miss was a canonical-hostname false negative. Remaining strict failures were snapshot transport churn and facade/navigation limitations, not lifecycle failures.
test plan
- Eve tests: 9/9
- Eve typecheck
- Eve consumer
eve build - Core tests on the base PR: 37/37
- Focused oxfmt/oxlint
No changeset is included because the Eve integration is a private example.
Summary by cubic
Eve now closes and releases its facade-owned Browserbase session when model code calls await browser.close(), preventing reuse of a closed session and surfacing cleanup failures instead of hiding them. Before, close returned while Eve kept the session; now we await local cleanup, release via the shared helper, and create fresh resources on the next call.
- Adds a close hook on StagehandFacadeTools so explicit close triggers Eve-side cleanup and calls
releaseBrowserbaseSessionfrom@browserbasehq/stagehand-integrations/facade(replacing the prior best-effort REST call). - Persists session metadata (apiKey, baseUrl, id) on the facade; clears the stored ID only after a successful release; a failed release marks the session suspect and throws
BrowserbaseSessionReleaseError. - Serializes cleanup with creation; reconnection skips a suspect session and launches fresh, and the next call after an explicit close creates new resources.
- Aggregates cleanup errors on explicit close: throws the single failure or an
AggregateErrorwhen multiple occur; a browser-close failure still surfaces even if remote release succeeds. - On Stagehand initialization failure after launch, attempts release and clears persisted ownership; marks the session suspect if release fails.
- Tests cover explicit close → fresh creation, failed release surfacing, browser-close failure with successful release, and init-failure cleanup.