Repository navigation
Browser crash (CHECK failure) when the refreshed New Tab Page loads in a DevTools-created isolated browser context #60146
Description
Description
The whole browser process crashes (deliberate CHECK failure, SIGTRAP) when the refreshed New Tab Page (brave_new_tab_page_refresh) loads in an off-the-record browser context that is not a regular Private window, such as a context created over the DevTools protocol with Target.createBrowserContext.
This hits anyone whose Brave is driven by automation that uses isolated contexts, for example Puppeteer's browser.createBrowserContext() or the Chrome DevTools MCP new_page tool with isolatedContext (used by AI coding agents). The crash takes down the user's main browser with all its windows. I hit it 17 times between Sep 28 and Oct 25, across 1.94.121 and 1.97.59.
Regular Private windows are not affected. They get the Private New Tab Page.
Steps to reproduce
- Start Brave with remote debugging and a fresh profile:
brave-browser --user-data-dir=$(mktemp -d) --remote-debugging-port=9333 --no-first-run about:blank
(I verified the clean-profile repro with--headless=new; my 17 real-world crashes were in normal windowed sessions and have the same crash PC.) - Over CDP, create an isolated browser context and open the New Tab Page in it:
Target.createBrowserContext→Target.createTarget { url: "chrome://newtab/", browserContextId } - The browser process dies within about a second.
Minimal Node 22+ script (no dependencies):
const { webSocketDebuggerUrl } = await (await fetch("http://127.0.0.1:9333/json/version")).json();
const ws = new WebSocket(webSocketDebuggerUrl);
await new Promise((r) => (ws.onopen = r));
let id = 0;
const send = (method, params = {}) => new Promise((r) => {
const myId = ++id;
ws.addEventListener("message", function h(e) {
const m = JSON.parse(e.data);
if (m.id === myId) { ws.removeEventListener("message", h); r(m.result); }
});
ws.send(JSON.stringify({ id: myId, method, params }));
});
const { browserContextId } = await send("Target.createBrowserContext");
await send("Target.createTarget", { url: "chrome://newtab/", browserContextId });
ws.onclose = () => console.log("browser crashed");Control: the same Target.createTarget call without browserContextId (default profile) loads the New Tab Page normally.
Actual result
The browser process terminates with SIGTRAP (Trace/breakpoint trap (core dumped)), and every window closes.
Expected result
The New Tab Page loads in the isolated context, or the Private New Tab Page is shown as in regular Private windows, and the browser keeps running.
Reproduces how often
Easily reproduced
Brave version (brave://version info)
Brave 1.97.59, Chromium 155.0.8059.85
Revision: c7357bfd2a76c2467ba1ae62593a81ae92c05205
OS: Linux, Ubuntu 24.04.5 LTS (x86_64), GNOME 46 on Wayland
Channel information
- release (stable)
- beta
- nightly
Reproducibility
- with Brave Shields disabled
- with Brave Rewards disabled
- in the latest version of Chrome
Miscellaneous information
Crash analysis (from local Crashpad minidumps and disassembly of the shipped 1.97.59 binary):
ptype=browser, crashing thread is the UI thread, signal 5 (int3; ud2= official-buildCHECK).- Crash PC is the same in every dump of a given build:
brave+0xb861553on 1.97.59 (3 of 3 dumps) andbrave+0xaec3e9bon 1.94.121 (14 of 14). A clean-profile repro on 1.97.59 run under gdb stopped at the samebrave+0xb861553. - Call path (frames identified from strings referenced by each function):
blink.mojom.BrowserInterfaceBroker→PerWebUIBrowserInterfaceBroker(per_web_ui_browser_interface_broker.cc) → binder forbrave_new_tab_page_refresh.mojom.NewTabPageHandler→ constructor of a helper that observesntp.custom_links_visible/ntp.shortcust_visible. That constructorCHECKs its first pointer argument, which is null. - That argument comes from a getter that appears to return
nullptrwhenProfile::IsOffTheRecord()is true. A DevTools-created context is off the record but not the primary incognito profile, so it seems to get the regular (refresh) NTP instead of the private one, and its handler is built without that dependency.
Workaround (verified): brave://flags/#brave-ntp-refresh-enabled → Disabled (--disable-features=BraveNewTabPageRefreshEnabled). With it disabled, the same repro loads the New Tab Page in isolated contexts without crashing.
Possibly related: #56326 (browser-process CHECK crash from New Tab Page shortcut tiles on macOS). Its crash keys also include tabdrag-event-source=mouse, which every one of my real-world dumps has too. I don't think tab dragging is the trigger: the clean-profile repro above involves no tab drag.
Local minidumps are available on request.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTo do
Activity
geniussingare commentedon Oct 25, 2026
I looked at the brave-core code path for this and I think the cause is a gap in how the New Tab Page variant is chosen. I haven't built or reproduced this locally, so please correct me where I'm wrong.
Where it goes wrong
BravePrivateNewTabUIConfig::IsWebUIEnabled()(browser/ui/webui/private_new_tab_page/brave_private_new_tab_ui.cc) returns true only forIsIncognitoProfile() || IsTor() || IsGuestSession() || IsEnterpriseIsolatedModeProfile().BraveWebUIControllerFactory(browser/ui/webui/brave_web_ui_controller_factory.cc,chrome::kChromeUINewTabHostbranch) assumes private profiles never get there (it has a DCHECK for that) and returnsBraveNewTabPageUIwhenkBraveNewTabPageRefreshEnabledis on.Target.createBrowserContextis off the record but apparently none of the four types above, so it gets the regular refreshed NTP.BraveNewTabPageUI::BindInterface(NewTabPageHandler)then constructsTopSitesFacade, whose constructor starts withCHECK(most_visited_sites_)(brave_new_tab_page_refresh/top_sites_facade.cc:45). It also observeskNtpShortcutsVisible/kNtpCustomLinksVisible, which matches the reporter's crash analysis. I believeChromeMostVisitedSitesFactory::NewForProfile()returns null for off-the-record profiles, but that's Chromium code and I haven't verified it.Possible fixes
IsPrivateNewTab()inbrave_new_tab_message_handler.ccconsistent. This gives the expected result in the report (Private NTP shown, no crash). One concern: a broadIsOffTheRecord()check must not catch the AI Chat agent profile, which the factory handles separately in the same branch.TopSitesFacadehandle a nullmost_visited_sites_(empty list, noCHECK), or haveBindInterfacereturn early for profiles that don't support it.A browser test that opens
chrome://newtabin an off-the-record, non-incognito profile would cover either fix.Maintainers: which direction do you prefer? I'm happy to work on a PR if this issue is free to pick up.