From 652a5e359c1923e207bfac4087e779753de9335f Mon Sep 17 00:00:00 2001 From: omartelo Date: Tue, 27 Oct 2026 20:59:22 -0300 Subject: [PATCH 1/2] fix(browser): hold a download's answer past Chrome's reload after an Chrome reloads the page 1.46 to 1.70 seconds after an begins (Chromium 155), and the reload aborts a navigation started before it. The 1.5-second hold sat inside that spread: the next navigate failed with net::ERR_ABORTED 2 times in 40. The hold is now 2.5 seconds. The live test also stopped polling at the first read naming a download, which can still be in progress (seen on Windows). The contract is that a read names a running download and the next one says when it ends, so the test now polls until the download is no longer in progress. --- docs/browser-tab.md | 5 +++-- docs/ceilings.md | 8 ++++---- internal/browser/downloads_live_test.go | 10 +++++++--- internal/browser/session_downloads.go | 11 ++++++----- 4 files changed, 20 insertions(+), 14 deletions(-) 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..ea6252be 100644 --- a/docs/ceilings.md +++ b/docs/ceilings.md @@ -994,10 +994,10 @@ work when nobody knows it and that the call site never shows. The mechanism and - **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 + download id sits there; it takes its real name once complete. 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. 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. From ffde6391ebde3ac40aaaedade7d72b34d22b400f Mon Sep 17 00:00:00 2001 From: omartelo Date: Tue, 27 Oct 2026 21:52:14 -0300 Subject: [PATCH 2/2] fix(browser): keep Chrome's model downloads out of the Downloads folder Downloads are allowed for the whole browser, so the optimization guide's model downloads, which run with no page and announce no download event, landed in the user's Downloads as files named by a GUID and were never renamed. The Windows and macOS CI runners left 4 KB to 33 MB of them next to the six files the live test expects. Chrome now starts with those features off. --- docs/ceilings.md | 19 +++++++++++-------- internal/browser/chrome.go | 12 +++++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/ceilings.md b/docs/ceilings.md index ea6252be..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 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. + `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