Mintplex-Labs/anything-llm · error · Error
No browser tab matching
Error message
No browser tab matching "${targetUrl}". Open tabs:
${openTabs} What it means
Thrown by cdp-eval when a targetUrl filter argument was supplied but no open page matches: matching is substring 'url.includes(targetUrl)' OR case-insensitive 'title.includes(targetUrl)'. The error lists every open tab (title + URL) to make the mismatch obvious. The default without a filter is the most recently opened page, so this only fires when an explicit tab selector was passed.
Solutions
- Read the 'Open tabs:' list in the error and re-issue with a substring that actually appears in one of the listed URLs (URLs are the stable choice)
- If the tab was closed, re-open/navigate to the target URL first, then retry with the same filter
- Prefer a distinctive URL fragment (hostname+path piece) over titles, which change with page state
- Drop the filter entirely to act on the most recent tab when any tab is acceptable
Example fix
# before: filter no longer matches after a redirect node cdp-eval.js 'console.log(1)' 'github.com/login' # after: use the post-login URL fragment shown in 'Open tabs:' node cdp-eval.js 'console.log(1)' 'github.com/dashboard'
Defensive patterns
Strategy: validation
Validate before calling
async function tabMatches(filter) {
const targets = await (await fetch('http://127.0.0.1:9222/json/list')).json();
const pages = targets.filter((t) => t.type === 'page' && !t.url.startsWith('chrome://') && !t.url.startsWith('devtools://'));
return pages.some((p) => p.url.includes(filter) || p.title.toLowerCase().includes(filter.toLowerCase()));
}
if (filter && !(await tabMatches(filter))) { /* re-open the page or drop the filter */ } Try / catch
try { await evalInPage(expr, filter); } catch (e) { if (/No browser tab matching/.test(e.message)) { /* e.message lists open tabs — pick a fresh substring from it and retry once */ } else throw e; } Prevention
- Prefer URL-fragment selectors over titles — titles change with SPA state, URLs survive longer
- Re-derive the selector after any navigation/redirect step instead of caching it for the whole session
- Parse the 'Open tabs:' list from the error to self-correct instead of failing the whole task
When it happens
Trigger: Invoking the eval helper with a URL/title fragment (e.g. 'github.com/myrepo') when no open tab contains that substring in URL or title; the tab was closed or navigated away between calls; typo/case mismatch in the URL fragment (URL match is case-sensitive, only title match lowercases); auth redirect changed the URL so the fragment no longer appears.
Common situations: Agent assumes a page is still open after a redirect or popup closed it; targeting by document title that changes dynamically (SPA title updates); passing a full title with different casing/emoji than the tab shows.
Related errors
- No browser pages open.
- No browser tab matching
- No webSocketDebuggerUrl — is Chromium running?
- click requires coordinates
- key requires argument (e.g. Enter, Tab, Escape)
AI-assisted analysis of Mintplex-Labs/anything-llm@3aec848f28 (2026-08-18).
Data as JSON: /api/errors/fc7f4f04bd268fab.
Report an issue: GitHub.
Appendix: source
Thrown at open-computer/services/interface-service/utils/cdp-eval.js:94
socket.on("open", () => { clearTimeout(t); resolve(socket); });
socket.on("error", (err) => { clearTimeout(t); reject(err); });
});
try {
const { targetInfos } = await cdpSend(ws, "Target.getTargets");
const pages = targetInfos.filter(
(t) => t.type === "page" && !t.url.startsWith("chrome://") && !t.url.startsWith("devtools://")
);
if (pages.length === 0) throw new Error("No browser pages open.");
let target = pages[pages.length - 1];
if (targetUrl) {
const match = pages.find(
(p) => p.url.includes(targetUrl) || p.title.toLowerCase().includes(targetUrl.toLowerCase())
);
if (!match) {
const openTabs = pages.map((p) => `- ${p.title} ${p.url}`).join("\n");
throw new Error(`No browser tab matching "${targetUrl}". Open tabs:\n${openTabs}`);
}
if (match) target = match;
}
const { sessionId } = await cdpSend(ws, "Target.attachToTarget", {
targetId: target.targetId,
flatten: true,
});
await cdpSend(ws, "Runtime.enable", {}, sessionId);
const evalResult = await cdpSend(ws, "Runtime.evaluate", {
expression: code,
returnByValue: true,
awaitPromise: true,
userGesture: true,
}, sessionId);
View on GitHub (pinned to 3aec848f28)