mastra-ai/mastra · error · Error
Cannot launch browser in '${this.status}' state
Error message
Cannot launch browser in '${this.status}' state What it means
MastraBrowser.launch() enforces a lifecycle state machine. If the browser instance is currently 'closing' or already 'closed', launch() refuses to start a new browser process and throws. A browser can only transition to 'launching' from 'pending' (or be awaited via an existing _launchPromise when already 'launching').
Source
Thrown at packages/core/src/browser/browser.ts:710
async launch(threadId?: string): Promise<void> {
// Set current thread if provided, so thread-scoped browsers launch for that thread
if (threadId !== undefined) {
this.setCurrentThread(threadId);
}
// Already ready
if (this.status === 'ready') {
return;
}
// Already launching - wait for existing promise
if (this.status === 'launching' && this._launchPromise) {
return this._launchPromise;
}
// Can't launch if closing/closed
if (this.status === 'closing' || this.status === 'closed') {
throw new Error(`Cannot launch browser in '${this.status}' state`);
}
this.status = 'launching';
this.error = undefined;
this._launchPromise = (async () => {
try {
await this.doLaunch();
this.status = 'ready';
// Fire onLaunch hook
if (this.config.onLaunch) {
await this.config.onLaunch({ browser: this });
}
// Notify onBrowserReady callbacks
this.notifyBrowserReady();
} catch (err) {View on GitHub (pinned to 75dd419e61)
Solutions
- Recreate the browser instance (new Browser(...)) instead of reusing a closed one.
- Call ensureReady() rather than launch() directly — it re-launches browsers that were previously closed.
- Serialize lifecycle: await all close()/in-flight close promises before issuing a new launch.
- Check this.status (or expose a helper) before calling launch().
Example fix
// before
await browser.close();
await browser.launch(); // throws
// after
await browser.close();
browser = new PlaywrightBrowser({ ...opts }); // or call ensureReady()
await browser.ensureReady(); Defensive patterns
Strategy: try-catch
Validate before calling
// guard before launching
if (browser.status === 'closing' || browser.status === 'closed') {
throw new Error(`Refusing to launch: browser is ${browser.status}; recreate the instance`);
}
await browser.launch(); Type guard
function canLaunch(b: { status: string }): b is { status: 'pending' | 'launching' } {
return b.status === 'pending' || b.status === 'launching';
} Try / catch
try {
await browser.ensureReady();
} catch (err) {
if (err instanceof Error && err.message.startsWith("Cannot launch browser in '")) {
browser = createBrowser(options); // recreate on closed/closing state
await browser.ensureReady();
} else throw err;
} Prevention
- Prefer ensureReady() over raw launch() — it handles closed browsers.
- Treat browser instances as single-use across close(): recreate after close.
- Serialize lifecycle calls; never fire close() and launch() concurrently.
- Log status transitions in custom providers to spot stale states.
When it happens
Trigger: Calling launch() (directly or via ensureReady) on a browser instance on which close()/closeSession has been called, or while a close is still in flight; calling launch() twice concurrently on the same instance after it was closed without resetting status to 'pending'.
Common situations: A long-lived browser object is closed by the app (or by a thread-session timeout) but reused later without recreating the instance; a race where one part of the app closes the browser while an agent request triggers ensureReady; calling connect after disconnecting.
Related errors
- Browser is ${this.status} and cannot be used
- tool_result must be preceded by a tool_call with the same to
- Factory kickoff was queued onto an ending run and never reac
- GitHub token refresh no longer matches the active Factory wo
- Factory workspace GitHub credential registration is no longe
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/7f276cd57f738d67.
Report an issue: GitHub.