diff --git a/docs/browser-tab.md b/docs/browser-tab.md
index 4f20e493..3bce6843 100644
--- a/docs/browser-tab.md
+++ b/docs/browser-tab.md
@@ -72,8 +72,9 @@ and size, after waiting up to ten seconds for it; one still running answers
`browser_navigate` on a URL that answers with a file download it the same way:
Chrome aborts that navigation and the page stays where it was, so the answer
is the handle with `downloads` rather than an error. That answer also
-waits until 1.5 seconds after the download began, because Chrome aborts a
-navigation started sooner and reloads the page. `browser-download` tells the
+waits until 2.5 seconds after the download began, because Chrome reloads the
+page about 1.5 seconds after an `` and the reload aborts a
+navigation started before it. `browser-download` tells the
window when a file is complete, and the window shows a toast naming it with a
button that opens its folder. The same holds in a window opened with Open in a
window.
diff --git a/docs/ceilings.md b/docs/ceilings.md
index e59e7a2f..2796af30 100644
--- a/docs/ceilings.md
+++ b/docs/ceilings.md
@@ -992,14 +992,17 @@ work when nobody knows it and that the call site never shows. The mechanism and
live in the window: closing it, or the browser, loses them.
- **A session browser's downloads go straight to the user's Downloads folder** (`internal/browser/downloads.go`,
- `session_downloads.go`, `docs/browser-tab.md`): `XDG_DOWNLOAD_DIR` on Linux, `~/Downloads` on macOS and
- Windows, even for a Windows user who moved that known folder. While one runs, a file named by Chrome's
- download id sits there; it takes its real name once complete. Chrome aborts a navigation the page starts
- within about a second of a download (`net::ERR_ABORTED`, then it reloads the page; Chromium 155, with
- downloads on or off), so an agent's click, key press, open or navigate that downloaded holds its answer
- 1.5 seconds; a navigation the person starts in the pane is not held. A download still running after ten
- seconds is reported in progress and named again when it ends. No tool lists past downloads or cancels one,
- nothing caps their size, and closing the browser cancels one still running.
+ `session_downloads.go`, `docs/browser-tab.md`): `XDG_DOWNLOAD_DIR` on Linux, `~/Downloads` on macOS and Windows,
+ even for a Windows user who moved that known folder. While one runs, a file named by Chrome's download id sits
+ there; it takes its real name once complete. The download behaviour is the whole browser's, so what Chrome
+ downloads on its own, with no page, lands there too under its id and is never renamed: the optimization guide's
+ model downloads are switched off (`disabledFeatures`, `chrome.go`), and a background download a later Chrome adds
+ would land. Chrome reloads the page 1.46 to 1.70 seconds after an `` begins, and the reload aborts a
+ navigation started before it (`net::ERR_ABORTED`; Chromium 155, with downloads on or off), so an agent's click,
+ key press, open or navigate that downloaded holds its answer 2.5 seconds; a navigation the person starts in the
+ pane is not held. A download still running after ten seconds is reported in progress and named again when it
+ ends. No tool lists past downloads or cancels one, nothing caps their size, and closing the browser cancels one
+ still running.
## Languages
diff --git a/internal/browser/chrome.go b/internal/browser/chrome.go
index a058ce36..ee76d125 100644
--- a/internal/browser/chrome.go
+++ b/internal/browser/chrome.go
@@ -48,7 +48,8 @@ func chromeLaunch(dir string, headed bool) (Page, error) {
return nil, err
}
opts := append([]chromedp.ExecAllocatorOption{}, chromedp.DefaultExecAllocatorOptions[:]...)
- opts = append(opts, chromedp.ExecPath(exe), chromedp.UserDataDir(dir), chromedp.WSURLReadTimeout(launchWait))
+ opts = append(opts, chromedp.ExecPath(exe), chromedp.UserDataDir(dir), chromedp.WSURLReadTimeout(launchWait),
+ chromedp.Flag("disable-features", disabledFeatures))
if headed {
opts = append(opts, chromedp.Flag("headless", false))
}
@@ -66,6 +67,15 @@ func chromeLaunch(dir string, headed bool) (Page, error) {
return p, nil
}
+// disabledFeatures replaces chromedp's own disable-features list, which it
+// repeats, adding the optimization guide's model downloads. Those go through
+// the profile's download manager with no page and no download event, so with
+// downloads allowed Chrome saved them into the user's Downloads as files
+// named by a GUID, 4 KB to 33 MB each (Chrome on the Windows and macOS CI
+// runners, 2026-10-27).
+const disabledFeatures = "site-per-process,Translate,BlinkGenPropertyTrees," +
+ "OptimizationGuideModelDownloading,OptimizationTargetPrediction"
+
type chromePage struct {
ctx context.Context
cancel context.CancelFunc
diff --git a/internal/browser/downloads_live_test.go b/internal/browser/downloads_live_test.go
index 4ee48726..0290b8ad 100644
--- a/internal/browser/downloads_live_test.go
+++ b/internal/browser/downloads_live_test.go
@@ -69,14 +69,18 @@ func TestChromeDownloadsLandInDownloadsAndThePageStays(t *testing.T) {
}
want := filepath.Join(dir, id+".csv")
got := info.Downloads
- for deadline := time.Now().Add(10 * time.Second); len(got) == 0 && time.Now().Before(deadline); {
- // A new tab hands its URL to the page after the click returns.
+ for deadline := time.Now().Add(10 * time.Second); (len(got) == 0 || got[0].State == api.DownloadInProgress) && time.Now().Before(deadline); {
+ // A new tab hands its URL to the page after the click returns,
+ // and a read names a download still running, then again once
+ // it ends.
time.Sleep(100 * time.Millisecond)
read, err := s.Read(t.Context(), api.SessionIDOptions{ID: "s"})
if err != nil {
t.Fatal(err)
}
- got = read.Downloads
+ if len(read.Downloads) > 0 {
+ got = read.Downloads
+ }
}
if len(got) != 1 || got[0].Path != want || got[0].State != api.DownloadCompleted {
t.Fatalf("downloads %+v, want %s completed", got, want)
diff --git a/internal/browser/session_downloads.go b/internal/browser/session_downloads.go
index 22f17d72..1ea0ac99 100644
--- a/internal/browser/session_downloads.go
+++ b/internal/browser/session_downloads.go
@@ -22,11 +22,12 @@ const downloadPoll = 50 * time.Millisecond
const downloadNotice = time.Second
// downloadQuiet is how long after a download begins a click or a key press
-// holds its answer. Chrome aborts a navigation the page starts within about a
-// second of a download (net::ERR_ABORTED, then it reloads the page): the
-// agent's next step failed 10 times in 10 right after an , and
-// none in 50 with this gap (Chromium 155, measured 2026-10-27).
-const downloadQuiet = 1500 * time.Millisecond
+// holds its answer. Chrome reloads the page 1.46 to 1.70 seconds after an
+// begins (24 samples, Chromium 155, measured 2026-10-27), and
+// the reload aborts a navigation the agent started before it
+// (net::ERR_ABORTED). A 1.5-second hold sat inside that spread and the next
+// navigate failed 2 times in 40; this one clears it.
+const downloadQuiet = 2500 * time.Millisecond
// download is one of a session's downloads and the state the agent last
// heard it in, "" before it heard of it.