Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ The profile directory stores:

**Tip**: Use different profile paths for different projects to keep their browser state isolated.

**Sessions with profiles**: When `--session` is combined with `--profile`, each session stores its Chrome data in a separate subdirectory: `<profile>/<session>/`. This ensures that concurrent sessions using the same base profile path each get their own isolated Chrome instance. For example, `--profile ~/.myapp-profile --session work` uses `~/.myapp-profile/work/` and `--session personal` uses `~/.myapp-profile/personal/`.

## Session Persistence

Alternatively, use `--session-name` to automatically save and restore cookies and localStorage across browser restarts:
Expand Down
72 changes: 70 additions & 2 deletions cli/src/native/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,19 @@ async fn auto_launch(state: &mut DaemonState) -> Result<(), String> {
Ok(())
}

/// Returns the effective Chrome user-data-dir path for a session.
///
/// Appends the session ID as a subdirectory under the base profile path so
/// that each `--session` daemon gets its own independent Chrome instance even
/// when `--profile` is shared across sessions. Chrome enforces a singleton
/// constraint per `--user-data-dir`, so without this isolation the second
/// daemon silently connects to the first daemon's Chrome instance instead of
/// launching a new one.
fn session_scoped_profile(profile: &str, session_id: &str) -> String {
let profile = profile.trim_end_matches('/');
format!("{}/{}", profile, session_id)
}

fn launch_options_from_env() -> LaunchOptions {
let headed = env::var("AGENT_BROWSER_HEADED")
.map(|v| v == "1" || v == "true")
Expand All @@ -959,7 +972,12 @@ fn launch_options_from_env() -> LaunchOptions {
executable_path: env::var("AGENT_BROWSER_EXECUTABLE_PATH").ok(),
proxy: env::var("AGENT_BROWSER_PROXY").ok(),
proxy_bypass: env::var("AGENT_BROWSER_PROXY_BYPASS").ok(),
profile: env::var("AGENT_BROWSER_PROFILE").ok(),
profile: env::var("AGENT_BROWSER_PROFILE").ok().map(|p| {
match env::var("AGENT_BROWSER_SESSION").ok() {
Some(session) => session_scoped_profile(&p, &session),
None => p,
}
}),
allow_file_access: env::var("AGENT_BROWSER_ALLOW_FILE_ACCESS")
.map(|v| v == "1" || v == "true")
.unwrap_or(false),
Expand Down Expand Up @@ -1148,7 +1166,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
profile: cmd
.get("profile")
.and_then(|v| v.as_str())
.map(|s| s.to_string()),
.map(|s| session_scoped_profile(s, &state.session_id)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.map(|s| session_scoped_profile(s, &state.session_id)),
.map(|s| match env::var("AGENT_BROWSER_SESSION").ok() {
Some(session) => session_scoped_profile(s, &session),
None => s.to_string(),
}),

handle_launch unconditionally appends session ID to profile paths, breaking backward compatibility when AGENT_BROWSER_SESSION is not set.

Fix on Vercel

allow_file_access: cmd
.get("allowFileAccess")
.and_then(|v| v.as_bool())
Expand Down Expand Up @@ -5884,6 +5902,56 @@ mod tests {
);
}

#[test]
fn test_session_scoped_profile_basic() {
assert_eq!(
session_scoped_profile("/tmp/my-profile", "first"),
"/tmp/my-profile/first"
);
}

#[test]
fn test_session_scoped_profile_default_session() {
assert_eq!(
session_scoped_profile("/tmp/my-profile", "default"),
"/tmp/my-profile/default"
);
}

#[test]
fn test_session_scoped_profile_strips_trailing_slash() {
assert_eq!(
session_scoped_profile("/tmp/my-profile/", "second"),
"/tmp/my-profile/second"
);
}

#[test]
fn test_launch_options_from_env_profile_with_session() {
let _guard = EnvGuard::new(&["AGENT_BROWSER_PROFILE", "AGENT_BROWSER_SESSION"]);
_guard.set("AGENT_BROWSER_PROFILE", "/tmp/test-profile");
_guard.set("AGENT_BROWSER_SESSION", "my-session");
let opts = launch_options_from_env();
assert_eq!(
opts.profile.as_deref(),
Some("/tmp/test-profile/my-session"),
"profile should include session subdirectory"
);
}

#[test]
fn test_launch_options_from_env_profile_without_session() {
let _guard = EnvGuard::new(&["AGENT_BROWSER_PROFILE", "AGENT_BROWSER_SESSION"]);
_guard.set("AGENT_BROWSER_PROFILE", "/tmp/test-profile");
_guard.remove("AGENT_BROWSER_SESSION"); // no session set
let opts = launch_options_from_env();
assert_eq!(
opts.profile.as_deref(),
Some("/tmp/test-profile"),
"profile should be unchanged when session is unset"
);
}

#[test]
fn test_har_entry_to_json_enriches_request_and_response() {
// wall_time: 2026-03-15T12:00:00Z = 1_773_576_000
Expand Down
1 change: 1 addition & 0 deletions cli/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2599,6 +2599,7 @@ Snapshot Options:
Authentication:
--profile <path> Persist login sessions across restarts (cookies, IndexedDB, cache)
(or AGENT_BROWSER_PROFILE env)
When used with --session, data is stored in <path>/<session>/
--session-name <name> Auto-save/restore cookies and localStorage by name
(or AGENT_BROWSER_SESSION_NAME env)
--state <path> Load saved auth state (cookies + storage) from JSON file
Expand Down
10 changes: 10 additions & 0 deletions docs/src/app/sessions/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ The profile directory stores:
- Browser cache
- Login sessions

**Using `--profile` with `--session`**: When combining `--profile` with `--session`, each session stores its Chrome data in a separate subdirectory (`<profile>/<session>/`). This ensures that concurrent sessions sharing the same base profile path each launch their own independent Chrome instance. For example:

```bash
# Two independent Chrome instances, each with their own persisted data
agent-browser --profile ~/.myapp-profile --session work open app.example.com
agent-browser --profile ~/.myapp-profile --session personal open app.example.com
# work data -> ~/.myapp-profile/work/
# personal data -> ~/.myapp-profile/personal/
```

## Import auth from your browser

If you are already logged in to a site in Chrome, you can grab that auth state and reuse it in agent-browser. This is the fastest way to bypass login flows, OAuth, SSO, or 2FA.
Expand Down
2 changes: 2 additions & 0 deletions skills/agent-browser/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ agent-browser --profile ~/.myapp open https://app.example.com/login
agent-browser --profile ~/.myapp open https://app.example.com/dashboard
```

When using `--profile` with `--session`, each session stores its Chrome data in a separate subdirectory (`<profile>/<session>/`) so that concurrent sessions with the same base profile path run independent Chrome instances without conflict.

**Option 3: Session name (auto-save/restore cookies + localStorage)**

```bash
Expand Down
Loading