chenglou/pretext · error · Error
${navigate.message ?? navigate.error}
Error message
${navigate.message ?? navigate.error} What it means
Thrown by the Firefox session's navigate() after sending a `browsingContext.navigate` command over the WebDriver BiDi WebSocket. If the BiDi response object carries an `error` field, the host throws using `navigate.message ?? navigate.error` — preferring the human-readable message and falling back to the raw error code. This is the Firefox counterpart of an AppleScript navigation failure: the BiDi peer (Firefox) accepted the command but reported it failed.
Source
Thrown at scripts/browser-automation.ts:565
function ensureState(): Promise<FirefoxSessionState> {
if (closed) {
return Promise.reject(new Error('Firefox automation session already closed'))
}
statePromise ??= initializeFirefoxSession()
return statePromise
}
return {
async navigate(url) {
const state = await ensureState()
const navigate = await state.bidi.send('browsingContext.navigate', {
context: state.context,
url,
wait: 'none',
})
if (navigate.error !== undefined) {
throw new Error(navigate.message ?? navigate.error)
}
},
async readLocationUrl() {
try {
const state = await ensureState()
const evaluation = await state.bidi.send('script.evaluate', {
expression: 'location.href',
target: { context: state.context },
awaitPromise: true,
resultOwnership: 'none',
})
if (evaluation.error !== undefined) {
return ''
}
return getBidiStringValue(evaluation)
} catch {
return ''
}View on GitHub (pinned to ac49b09b7d)
Solutions
- Read the exact `navigate.message ?? navigate.error` text in the thrown error — BiDi error codes (e.g. "no such frame", "invalid argument") point directly at the cause.
- Validate the URL is absolute and well-formed before calling session.navigate (the URL builders in corpus-check/font-matrix encode pieces, but a bad upstream value can still slip through).
- Confirm the Firefox process backing state.firefoxProcess is still alive; if it exited, recreate the session via createBrowserSession('firefox').
- If `wait: 'none'` is the issue on a newer Firefox, retry the navigation once after a short delay — some BiDi builds reject a second navigate issued before the first acknowledges.
- Check that state.context still references an open tab by issuing a harmless `script.evaluate` (readLocationUrl already swallows errors) before re-navigating.
Example fix
// before
const navigate = await state.bidi.send('browsingContext.navigate', {
context: state.context,
url,
wait: 'none',
})
if (navigate.error !== undefined) {
throw new Error(navigate.message ?? navigate.error)
}
// after — keep both fields and add the target url for diagnostics
if (navigate.error !== undefined) {
const detail = navigate.message ?? navigate.error
throw new Error(`Firefox BiDi navigate to ${url} failed: ${detail}`)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate URL shape before handing it to the Firefox session
function isValidNavigationUrl(url: string): boolean {
try {
const parsed = new URL(url)
return parsed.protocol === 'http:' || parsed.protocol === 'https:' || parsed.protocol === 'about:'
} catch {
return false
}
}
if (!isValidNavigationUrl(url)) throw new Error(`Refusing to navigate Firefox to invalid URL: ${url}`) Type guard
// Narrow a BiDi response into success/error channels
function isBidiError(res: { error?: unknown; message?: unknown }): res is { error: string; message?: string } {
return typeof res.error === 'string'
} Try / catch
try {
await session.navigate(url)
} catch (error) {
if (error instanceof Error && /BiDi|navigate/i.test(error.message)) {
// Likely stale context or rejected URL — recreate the Firefox session once, then rethrow if it still fails
session.close()
session = createBrowserSession('firefox')
await session.navigate(url)
} else {
throw error
}
} Prevention
- URL-encode every query parameter when building corpus/accuracy URLs so BiDi never sees an invalid absolute URL.
- Keep the Firefox process alive across navigations; if you spawn it per-run, recreate the session rather than reusing a stale context.
- Treat any BiDi response with an `error` field as terminal for that navigation — do not silently swallow and continue, the next call will likely fail too.
When it happens
Trigger: state.bidi.send('browsingContext.navigate', { context, url, wait: 'none' }) resolves with an object whose `error` is defined. Concrete causes: url is not a valid absolute URL (BiDi rejects relative/empty); the browsingContext id in state.context was closed (tab quit, Firefox restarted); Firefox is shutting down or crashed mid-navigation; a navigation to an about: or chrome:// URL blocked by BiDi; the BiDi implementation returned a protocol-level error like "invalid argument".
Common situations: Firefox process was killed or crashed between session creation and navigate (state.context now stale); the corpus/accuracy URL builder produced a malformed string (unencoded query, missing requestId); Firefox version upgrade changed BiDi error envelopes so `message` is undefined and only `error` (a terse code) is shown; navigating while a previous navigation on the same context has not settled and the BiDi server rejects the overlap.
Related errors
- Timed out waiting for local port ${port}
- Timed out waiting for local Bun server on ${baseUrl}
- ${session.message ?? session.error}
- ${tree.message ?? tree.error}
- Firefox BiDi returned no browsing context
AI-assisted analysis of chenglou/pretext@ac49b09b7d (2026-08-12).
Data as JSON: /api/errors/5e0e3a0937ad0ac5.
Report an issue: GitHub.