mastra-ai/mastra · error · Error
Browser is ${this.status} and cannot be used
Error message
Browser is ${this.status} and cannot be used What it means
ensureReady() verifies the browser is usable before delegating to launch(). It recovers from 'pending' and from a completed close (re-launches), but if the status is anything else it cannot handle — typically 'launching' without a tracked launch promise, or another unrecoverable state — it throws with the current status in the message.
Source
Thrown at packages/core/src/browser/browser.ts:861
// Reset to pending to allow re-launch after close
if (this.status === 'closed') {
this.status = 'pending';
}
await this.launch();
return;
}
if (this.status === 'launching') {
await this._launchPromise;
return;
}
if (this.status === 'closing') {
// Wait for close to complete, then re-launch
await this._closePromise;
this.status = 'pending';
await this.launch();
return;
}
throw new Error(`Browser is ${this.status} and cannot be used`);
}
/**
* Check if the browser is still alive.
* Override in subclass to detect externally closed browsers.
* @returns true if browser is alive, false if it was externally closed
*/
protected async checkBrowserAlive(): Promise<boolean> {
// Default implementation assumes browser is alive if status is ready
return true;
}
/**
* Check if the browser is currently running.
* @param _threadId - Thread identifier (for thread-scoped browsers)
*/
isBrowserRunning(_threadId?: string): boolean {
return this.status === 'ready';View on GitHub (pinned to 75dd419e61)
Solutions
- Log/inspect this.status at the throw site and reset it to 'pending' before retrying.
- Avoid manually mutating this.status in provider subclasses; use the base-class lifecycle methods.
- Recreate the browser instance if its state is unrecoverable.
- Add lifecycle locking so only launch()/close() change status.
Example fix
// before (custom provider) this.status = 'launching'; // manual mutation, promise not tracked // after await this.launch(); // base class sets status and tracks _launchPromise
Defensive patterns
Strategy: retry
Validate before calling
// only proceed on known-good states
const usable = ['pending', 'ready', 'closed'];
if (!usable.includes(browser.status)) {
throw new Error(`Unexpected browser status ${browser.status}; aborting operation`);
}
await browser.ensureReady(); Type guard
function isUsableStatus(s: string): boolean {
return ['pending', 'ready', 'closed'].includes(s);
} Try / catch
try {
await browser.ensureReady();
} catch (err) {
if (err instanceof Error && /^Browser is .+ and cannot be used$/.test(err.message)) {
// state machine corrupted: recreate instead of retrying same instance
browser = createBrowser(options);
await browser.ensureReady();
} else throw err;
} Prevention
- Never mutate this.status from outside the base-class lifecycle methods.
- Route all browser use through ensureReady(), not direct method calls.
- Add a mutex/queue around launch/close to keep status transitions atomic.
- Alert on unknown status values in logs.
When it happens
Trigger: Calling an operation that goes through ensureReady() while the browser is in an intermediate/unexpected status (e.g. status set to 'launching' by another code path, or a custom provider subclass mutating this.status manually and leaving it in a non-standard state).
Common situations: Custom provider subclasses that manage this.status themselves and leave stale values; concurrent lifecycle calls where status was set but the launch promise was cleared; corrupted state after a partially failed close/re-launch cycle.
Related errors
- Cannot launch browser in '${this.status}' state
- tool_result must be preceded by a tool_call with the same to
- MastraFactory.prepare() called twice
- Factory kickoff lease was lost before completion.
- Unsupported Factory stage: ${stage}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/4862b808c2c4acfa.
Report an issue: GitHub.