From d12e0134f97b9522d28e07445e27aea353468588 Mon Sep 17 00:00:00 2001 From: robertjamesprior <83608739+robertjamesprior@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:40:12 +0000 Subject: [PATCH] Document acquire-time profile binding for browser pools --- browsers/pools.mdx | 35 +++++++++++----------------- browsers/profiles/concurrency.mdx | 5 ++-- browsers/profiles/save-and-reuse.mdx | 4 ++++ reference/cli/browser-pools.mdx | 11 ++++++++- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/browsers/pools.mdx b/browsers/pools.mdx index d83958ee..2c88d3d2 100644 --- a/browsers/pools.mdx +++ b/browsers/pools.mdx @@ -205,37 +205,34 @@ As a best practice, release each browser when you're done with it — that retur ## Profiles with browser pools -A [profile](/browsers/profiles) carries login state, including cookies and local storage, into a browser. Use [Managed Auth](/auth/managed-auth) to populate that state and monitor its health. Put the profile on the browser pool when every browser should share one identity; leave it off and attach it after acquiring when each task needs its own (see [Per-user profiles with browser pools](#per-user-profiles-with-browser-pools)). +A [profile](/browsers/profiles) carries login state, including cookies and local storage, into a browser. Use [Managed Auth](/auth/managed-auth) to populate that state and monitor its health. Put the profile on the browser pool when every browser shares one identity; leave it off and bind one at acquire when each task needs its own (see [Per-user profiles with browser pools](#per-user-profiles-with-browser-pools)). A profile attached to the pool is loaded **read-only**. Every browser in the pool shares it, so `save_changes` doesn't apply and is silently ignored if sent — this prevents concurrent writes from corrupting the profile. ### Per-user profiles with browser pools -Because that profile is shared and read-only, it can't hold per-user login state for many users at once. To serve many users from one browser pool, create it with no profile — stealth, proxies, extensions, and viewport still live on the pool — then attach each user's profile to the browser *after* you acquire it, and release with `reuse: false` so the browser is destroyed. Destroying it keeps that user's state from reaching the next acquirer. +Because that profile is shared and read-only, it can't hold per-user login state for many users at once. To serve many users from one browser pool, create it with no profile — stealth, proxies, extensions, and viewport still live on the pool — then bind each user's profile on the acquire call. The browser is destroyed when the lease ends instead of returning to the pool, which keeps that user's state from reaching the next acquirer. -The read-only rule covers the pool's own profile, not one attached after acquiring: that profile belongs to the browser, so `save_changes` applies as it does on any other browser. Pass `save_changes: true` when you attach it — it defaults to `false`, and without it the browser is destroyed on release without writing the user's session back. +Binding at acquire loads the profile before the browser is handed to you, so you connect to a browser that already has the user's state. There is no second call to make, and no Chromium restart after your CDP client has connected. + +The read-only rule covers the pool's own profile, not one bound at acquire: that profile belongs to the browser, so `save_changes` applies as it does on any other browser. Pass `save_changes: true` when you bind it — it defaults to `false`, and without it the browser is destroyed on release without writing the user's session back. ```typescript Typescript/Javascript -const browser = await kernel.browserPools.acquire("my-pool"); - -await kernel.browsers.update(browser.session_id, { - profile: { name: "user-8f21c3", save_changes: true } +const browser = await kernel.browserPools.acquire("my-pool", { + profile: { name: "user-8f21c3", save_changes: true }, }); // ... drive the browser as that user ... await kernel.browserPools.release("my-pool", { session_id: browser.session_id, - reuse: false, }); ``` ```python Python -browser = kernel.browser_pools.acquire("my-pool") - -kernel.browsers.update( - browser.session_id, +browser = kernel.browser_pools.acquire( + "my-pool", profile={"name": "user-8f21c3", "save_changes": True}, ) @@ -244,22 +241,17 @@ kernel.browsers.update( kernel.browser_pools.release( "my-pool", session_id=browser.session_id, - reuse=False, ) ``` ```go Go -browser, err := client.BrowserPools.Acquire(ctx, "my-pool", kernel.BrowserPoolAcquireParams{}) -if err != nil { - panic(err) -} - -if _, err := client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdateParams{ +browser, err := client.BrowserPools.Acquire(ctx, "my-pool", kernel.BrowserPoolAcquireParams{ Profile: shared.BrowserProfileParam{ Name: kernel.String("user-8f21c3"), SaveChanges: kernel.Bool(true), }, -}); err != nil { +}) +if err != nil { panic(err) } @@ -267,14 +259,13 @@ if _, err := client.Browsers.Update(ctx, browser.SessionID, kernel.BrowserUpdate if err := client.BrowserPools.Release(ctx, "my-pool", kernel.BrowserPoolReleaseParams{ SessionID: browser.SessionID, - Reuse: kernel.Bool(false), }); err != nil { panic(err) } ``` -A profile can only be loaded into a browser that was created without one, which is why the pool itself has to stay profile-free. +A profile can only be loaded into a browser that was created without one, which is why the pool itself has to stay profile-free. An acquire-time profile always ends the lease by destroying the browser rather than returning it to the pool, so `reuse` does not apply. If you need to bind a profile to a browser that is already running, use [`browsers.update`](/browsers/profiles/save-and-reuse#load-a-profile-after-browser-creation) — that path restarts Chromium and disconnects your CDP client, so binding at acquire is the better default. ### Refresh on profile update diff --git a/browsers/profiles/concurrency.mdx b/browsers/profiles/concurrency.mdx index 0d1ca653..d0d8f723 100644 --- a/browsers/profiles/concurrency.mdx +++ b/browsers/profiles/concurrency.mdx @@ -116,9 +116,8 @@ A profile configured directly on a [browser pool](/browsers/pools#profiles-with- For per-user durable state: 1. Create the pool without a profile. -2. Acquire a browser. -3. Attach the user's profile with `save_changes: true`. -4. Release the browser with `reuse: false` so that user's state can't reach the next acquirer. +2. Bind the user's profile on the acquire call, with `save_changes: true`. +3. Drive the browser, then release it. An acquire-time profile always destroys the browser instead of returning it to the pool, so that user's state can't reach the next acquirer. See [Per-user profiles with browser pools](/browsers/pools#per-user-profiles-with-browser-pools) for complete examples. diff --git a/browsers/profiles/save-and-reuse.mdx b/browsers/profiles/save-and-reuse.mdx index b0b922e7..6214baa8 100644 --- a/browsers/profiles/save-and-reuse.mdx +++ b/browsers/profiles/save-and-reuse.mdx @@ -212,6 +212,10 @@ Set `start_url` when you create a browser if your automation requires a specific You can attach a profile to a running browser that was created without one. Loading the profile restarts Chromium, so reconnect your CDP or Playwright client afterward. + +If the browser comes from a [browser pool](/browsers/pools#per-user-profiles-with-browser-pools), bind the profile on the acquire call instead. It loads before the browser is returned, so your client connects to a browser that already has the profile rather than being restarted underneath it. + + ```typescript TypeScript const browser = await kernel.browsers.create(); diff --git a/reference/cli/browser-pools.mdx b/reference/cli/browser-pools.mdx index 4a6b6648..f02a6acc 100644 --- a/reference/cli/browser-pools.mdx +++ b/reference/cli/browser-pools.mdx @@ -72,15 +72,24 @@ Acquire a browser from the pool. | Flag | Description | |------|-------------| | `--timeout ` | Acquire timeout in seconds. | +| `--profile-id ` | Profile to load before the acquired browser is returned. The pool must not already have a profile. | +| `--profile-name ` | Profile to load before the acquired browser is returned, by name. Mutually exclusive with `--profile-id`. | +| `--save-changes` | Persist changes back to the acquire-time profile when the session ends. Defaults to `false`; without it the browser is destroyed on release without saving. | | `--output json`, `-o json` | Output raw JSON object. | +A browser that loads an acquire-time profile is destroyed and replaced on release rather than returned to the pool, so profile state can't leak into another lease. See [Per-user profiles with browser pools](/browsers/pools#per-user-profiles-with-browser-pools). + +```bash +kernel browser-pools acquire my-pool --profile-name user-8f21c3 --save-changes +``` + ## `kernel browser-pools release ` Release a browser back to the pool. | Flag | Description | |------|-------------| | `--session-id ` | Browser session ID to release. | -| `--reuse` | Reuse the browser instance (default: `true`). | +| `--reuse` | Reuse the browser instance (default: `true`). Ignored for a lease that loaded an acquire-time profile, which is always destroyed. | ## `kernel browser-pools delete ` Delete a browser pool.