Skip to content

feat(browser): persist login sessions across restarts#45371

Closed
John-Rood wants to merge 2 commits into
openclaw:mainfrom
John-Rood:feat/browser-session-persistence
Closed

feat(browser): persist login sessions across restarts#45371
John-Rood wants to merge 2 commits into
openclaw:mainfrom
John-Rood:feat/browser-session-persistence

Conversation

@John-Rood

Copy link
Copy Markdown
Contributor

Problem

The managed browser (openclaw profile) loses all login sessions on gateway restart. Chrome's in-memory cookie store isn't flushed to disk before the process gets SIGKILL'd after the 2.5s timeout.

Evidence: The Cookies SQLite DB has only tracking cookies — zero Google/GitHub/actual session cookies. exit_type: "Crashed" in Preferences confirms Chrome never shuts down cleanly.

Solution

Periodic cookie snapshots via CDP, sidestepping Chrome's shutdown behavior entirely.

How it works

  1. Every 60s: Network.getAllCookies() → save to <userDataDir>/openclaw-saved-state.json
  2. On shutdown: One last save before SIGTERM (best-effort, non-blocking)
  3. On launch: Read saved state → filter expired cookies → Network.setCookies()

Key design decisions

  • Atomic writes (tmp + rename) — crash mid-save can't corrupt the file
  • Best-effort everywhere — save/restore failures are logged as warnings, never block launch or shutdown
  • Expired cookie filtering — stale cookies are dropped on restore
  • Cleanup on exit — periodic timer stops when browser process exits

Files changed

File Change
src/browser/session-persistence.ts NEW — save, restore, periodic save logic
src/browser/session-persistence.test.ts NEW — 7 tests (format, expiry filtering, atomic write, cleanup)
src/browser/chrome.ts Restore on launch, save before shutdown
src/browser/server-context.availability.ts Wire periodic save timer on browser start/stop
src/browser/server-context.types.ts Add stopPeriodicSave to ProfileRuntimeState
src/config/types.browser.ts Add browser.sessionPersistence config type

Config

{
  "browser": {
    "sessionPersistence": {
      "enabled": true,
      "intervalMs": 60000
    }
  }
}

Both default to the values shown. Most users won't need to touch this.

What this covers

  • Google, GitHub, and all cookie-based login sessions
  • Survives gateway restarts, updates, reboots
  • Survives hard crashes (SIGKILL) — last periodic save is ≤60s old

Testing

  • 7 unit tests passing ✅
  • Build passes ✅
  • Manual testing: log into Google → wait 60s → restart gateway → session restored

…okie snapshots

The managed browser loses all login sessions on gateway restart because
Chrome's in-memory cookie store isn't flushed before SIGKILL. This adds:

- Periodic cookie save (every 60s) via CDP Network.getAllCookies()
- Cookie restore on browser launch via CDP Network.setCookies()
- Best-effort save before shutdown (before SIGTERM)
- Atomic writes (tmp + rename) to prevent corruption
- Config type: browser.sessionPersistence.enabled (default: true)

State file: <userDataDir>/openclaw-saved-state.json

Fixes login persistence for Google, GitHub, and all web sessions.
@greptile-apps

greptile-apps Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds periodic cookie snapshot persistence to the managed browser profile, solving the problem of login sessions being lost on gateway restart due to Chrome's in-memory cookie store never being flushed to disk (process is SIGKILL'd). The approach—CDP Network.getAllCookies every 60 s + restore on launch—is a reasonable workaround for Chrome's shutdown behavior.

Key findings:

  • sessionPersistence.enabled / intervalMs config values are never read. The config type is added in types.browser.ts but neither field is consumed in server-context.availability.ts or elsewhere. Setting enabled: false will not disable persistence, and a custom intervalMs will be silently ignored.
  • setInterval + async callback can produce concurrent saves. If a CDP call takes longer than the configured interval, two save callbacks can run in parallel and both write to the same .tmp file, defeating the atomicity guarantee.
  • Misleading log when all restored cookies are expired. restoreBrowserState returns { cookieCount: 0, savedAt: ... } (non-null) when a state file exists but every cookie is expired. The chrome.ts caller logs "🍪 restored 0 cookies" in that case, which is confusing.

Confidence Score: 3/5

  • The PR is functional for the happy path, but the config being a no-op and the potential concurrent-save race condition should be addressed before merging.
  • The core CDP-based save/restore mechanism is sound and best-effort error handling is present throughout. However, the sessionPersistence.enabled and intervalMs config fields are completely inoperative (never read), meaning users cannot disable or tune the feature as advertised. There is also a real race condition where a slow CDP call during periodic save can overlap with the next interval tick and corrupt the atomic write. These are correctness issues that warrant fixes before this lands.
  • src/browser/server-context.availability.ts (config not consumed) and src/browser/session-persistence.ts (concurrent-save guard missing).
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/browser/server-context.availability.ts
Line: 83-93

Comment:
**`sessionPersistence` config is never read**

The `browser.sessionPersistence.enabled` and `browser.sessionPersistence.intervalMs` config fields are declared in `types.browser.ts` but are never consumed here (or anywhere else in the codebase). This means:

- Setting `browser.sessionPersistence.enabled: false` will have **no effect** — persistence is always started unconditionally.
- Setting `browser.sessionPersistence.intervalMs` will also be silently ignored — `startPeriodicSave` will always use the 60-second default.

The `profile` object passed into this closure comes from resolved config. The raw config (and its `sessionPersistence` key) needs to be threaded through to `startSessionPersistence`, and both the `enabled` guard and `intervalMs` should be respected before calling `startPeriodicSave`.

```
// Example fix sketch
const persistenceCfg = opts.getState().resolved.sessionPersistence;
if (persistenceCfg?.enabled === false) return;
const stopSave = startPeriodicSave(
  ...,
  persistenceCfg?.intervalMs,  // pass through configured interval
);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/browser/session-persistence.ts
Line: 127-142

Comment:
**Concurrent saves possible with `setInterval` + async callback**

`setInterval` fires every `intervalMs` regardless of whether the previous async callback has finished. If a CDP call is slow (e.g. the browser is under load, or the network is congested), two invocations of the callback can run concurrently and both attempt to write to the same `.tmp` file simultaneously — defeating the atomicity guarantee.

For example, if save #1 is still awaiting `getAllCookiesViaCdp` when the interval fires again, save #2 will call `fs.writeFileSync(tmpPath, ...)` over the partial write of save #1, then `fs.renameSync` before save #1 finishes its own rename — resulting in a corrupted or wrong final file.

Consider tracking an in-flight save and skipping the tick if one is already running:

```typescript
let saving = false;
const timer = setInterval(async () => {
  if (saving) return;
  saving = true;
  try {
    const wsUrl = await getCdpWsUrl();
    if (!wsUrl) return;
    const result = await saveBrowserState(wsUrl, userDataDir);
    if (result.cookieCount > 0) {
      log.info(`🍪 saved ${result.cookieCount} cookies`);
    }
  } catch (err) {
    log.warn(`cookie periodic save failed: ${String(err)}`);
  } finally {
    saving = false;
  }
}, intervalMs);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/browser/session-persistence.ts
Line: 97-105

Comment:
**Misleading return value when all cookies are expired**

When a state file exists with cookies but all of them have expired, `validCookies.length` will be `0`. The function skips `setCookiesViaCdp` (correct), but still returns `{ cookieCount: 0, savedAt: state.savedAt }` (non-null).

The caller in `chrome.ts` does `if (restored) { log.info(...restored ${restored.cookieCount} cookies...) }` — so it will log `"🍪 restored 0 cookies (saved ...)"` which is confusing and implies a restore happened when it didn't.

Consider returning `null` in this case, or adjusting the log in `chrome.ts` to only log when `cookieCount > 0`:

```typescript
  if (validCookies.length === 0) {
    return null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 6d46c86

Comment on lines +83 to +93
const stopSave = startPeriodicSave(
async () => {
try {
return await getChromeWebSocketUrl(profile.cdpUrl);
} catch {
return null;
}
},
resolveOpenClawUserDataDir(profile.name),
persistenceLog,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sessionPersistence config is never read

The browser.sessionPersistence.enabled and browser.sessionPersistence.intervalMs config fields are declared in types.browser.ts but are never consumed here (or anywhere else in the codebase). This means:

  • Setting browser.sessionPersistence.enabled: false will have no effect — persistence is always started unconditionally.
  • Setting browser.sessionPersistence.intervalMs will also be silently ignored — startPeriodicSave will always use the 60-second default.

The profile object passed into this closure comes from resolved config. The raw config (and its sessionPersistence key) needs to be threaded through to startSessionPersistence, and both the enabled guard and intervalMs should be respected before calling startPeriodicSave.

// Example fix sketch
const persistenceCfg = opts.getState().resolved.sessionPersistence;
if (persistenceCfg?.enabled === false) return;
const stopSave = startPeriodicSave(
  ...,
  persistenceCfg?.intervalMs,  // pass through configured interval
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/browser/server-context.availability.ts
Line: 83-93

Comment:
**`sessionPersistence` config is never read**

The `browser.sessionPersistence.enabled` and `browser.sessionPersistence.intervalMs` config fields are declared in `types.browser.ts` but are never consumed here (or anywhere else in the codebase). This means:

- Setting `browser.sessionPersistence.enabled: false` will have **no effect** — persistence is always started unconditionally.
- Setting `browser.sessionPersistence.intervalMs` will also be silently ignored — `startPeriodicSave` will always use the 60-second default.

The `profile` object passed into this closure comes from resolved config. The raw config (and its `sessionPersistence` key) needs to be threaded through to `startSessionPersistence`, and both the `enabled` guard and `intervalMs` should be respected before calling `startPeriodicSave`.

```
// Example fix sketch
const persistenceCfg = opts.getState().resolved.sessionPersistence;
if (persistenceCfg?.enabled === false) return;
const stopSave = startPeriodicSave(
  ...,
  persistenceCfg?.intervalMs,  // pass through configured interval
);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +127 to +142
const timer = setInterval(async () => {
try {
const wsUrl = await getCdpWsUrl();
if (!wsUrl) {
return;
} // browser not running
const result = await saveBrowserState(wsUrl, userDataDir);
if (result.cookieCount > 0) {
log.info(`🍪 saved ${result.cookieCount} cookies`);
}
} catch (err) {
log.warn(`cookie periodic save failed: ${String(err)}`);
}
}, intervalMs);

return () => clearInterval(timer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Concurrent saves possible with setInterval + async callback

setInterval fires every intervalMs regardless of whether the previous async callback has finished. If a CDP call is slow (e.g. the browser is under load, or the network is congested), two invocations of the callback can run concurrently and both attempt to write to the same .tmp file simultaneously — defeating the atomicity guarantee.

For example, if save #1 is still awaiting getAllCookiesViaCdp when the interval fires again, save #2 will call fs.writeFileSync(tmpPath, ...) over the partial write of save #1, then fs.renameSync before save #1 finishes its own rename — resulting in a corrupted or wrong final file.

Consider tracking an in-flight save and skipping the tick if one is already running:

let saving = false;
const timer = setInterval(async () => {
  if (saving) return;
  saving = true;
  try {
    const wsUrl = await getCdpWsUrl();
    if (!wsUrl) return;
    const result = await saveBrowserState(wsUrl, userDataDir);
    if (result.cookieCount > 0) {
      log.info(`🍪 saved ${result.cookieCount} cookies`);
    }
  } catch (err) {
    log.warn(`cookie periodic save failed: ${String(err)}`);
  } finally {
    saving = false;
  }
}, intervalMs);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/browser/session-persistence.ts
Line: 127-142

Comment:
**Concurrent saves possible with `setInterval` + async callback**

`setInterval` fires every `intervalMs` regardless of whether the previous async callback has finished. If a CDP call is slow (e.g. the browser is under load, or the network is congested), two invocations of the callback can run concurrently and both attempt to write to the same `.tmp` file simultaneously — defeating the atomicity guarantee.

For example, if save #1 is still awaiting `getAllCookiesViaCdp` when the interval fires again, save #2 will call `fs.writeFileSync(tmpPath, ...)` over the partial write of save #1, then `fs.renameSync` before save #1 finishes its own rename — resulting in a corrupted or wrong final file.

Consider tracking an in-flight save and skipping the tick if one is already running:

```typescript
let saving = false;
const timer = setInterval(async () => {
  if (saving) return;
  saving = true;
  try {
    const wsUrl = await getCdpWsUrl();
    if (!wsUrl) return;
    const result = await saveBrowserState(wsUrl, userDataDir);
    if (result.cookieCount > 0) {
      log.info(`🍪 saved ${result.cookieCount} cookies`);
    }
  } catch (err) {
    log.warn(`cookie periodic save failed: ${String(err)}`);
  } finally {
    saving = false;
  }
}, intervalMs);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +97 to +105
const validCookies = state.cookies.filter(
(c) => c.session || c.expires === -1 || c.expires === 0 || c.expires > now,
);

if (validCookies.length > 0) {
await setCookiesViaCdp(cdpWsUrl, validCookies);
}

return { cookieCount: validCookies.length, savedAt: state.savedAt };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Misleading return value when all cookies are expired

When a state file exists with cookies but all of them have expired, validCookies.length will be 0. The function skips setCookiesViaCdp (correct), but still returns { cookieCount: 0, savedAt: state.savedAt } (non-null).

The caller in chrome.ts does if (restored) { log.info(...restored ${restored.cookieCount} cookies...) } — so it will log "🍪 restored 0 cookies (saved ...)" which is confusing and implies a restore happened when it didn't.

Consider returning null in this case, or adjusting the log in chrome.ts to only log when cookieCount > 0:

  if (validCookies.length === 0) {
    return null;
  }
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/browser/session-persistence.ts
Line: 97-105

Comment:
**Misleading return value when all cookies are expired**

When a state file exists with cookies but all of them have expired, `validCookies.length` will be `0`. The function skips `setCookiesViaCdp` (correct), but still returns `{ cookieCount: 0, savedAt: state.savedAt }` (non-null).

The caller in `chrome.ts` does `if (restored) { log.info(...restored ${restored.cookieCount} cookies...) }` — so it will log `"🍪 restored 0 cookies (saved ...)"` which is confusing and implies a restore happened when it didn't.

Consider returning `null` in this case, or adjusting the log in `chrome.ts` to only log when `cookieCount > 0`:

```typescript
  if (validCookies.length === 0) {
    return null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d46c867d6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/browser/chrome.ts
try {
const wsUrl = await getChromeWebSocketUrl(cdpUrlForPort(running.cdpPort), 500);
if (wsUrl) {
await saveBrowserState(wsUrl, running.userDataDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Bound pre-shutdown cookie save with a timeout

The shutdown path now awaits saveBrowserState before sending SIGTERM, but this call can wait indefinitely if CDP accepts the socket and never replies to Network.getAllCookies (there is no per-command timeout in the CDP helper). In that state, stopOpenClawChrome can hang and never reach the existing CHROME_STOP_TIMEOUT_MS kill logic, so restarts/shutdowns can stall exactly when the browser is unhealthy.

Useful? React with 👍 / 👎.

Comment on lines 103 to +105
const attachRunning = (running: NonNullable<ProfileRuntimeState["running"]>) => {
setProfileRunning(running);
startSessionPersistence();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Wire sessionPersistence config into runtime behavior

The new browser.sessionPersistence settings are declared but not honored in the runtime path: browser attach always starts persistence, and the periodic saver is started with default parameters. I checked usage with repo search (sessionPersistence only appears in the type declaration), so enabled: false and custom intervalMs values currently have no effect despite being exposed as config knobs.

Useful? React with 👍 / 👎.

- Read sessionPersistence config (enabled/intervalMs) instead of hardcoding
- Add concurrent-save guard to prevent overlapping periodic saves
- Return null from restoreBrowserState when all cookies are expired
@openclaw-barnacle openclaw-barnacle Bot added the agents Agent runtime and tooling label Mar 13, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 040aa5f64a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 112 to 114
running.proc.on("exit", () => {
stopSessionPersistence();
// Guard against server teardown (e.g., SIGUSR1 restart)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Scope timer cleanup to the exiting browser process

The new exit handler clears session persistence before verifying which process exited. In restart paths we can launch a replacement browser and start a new periodic-save timer before the old process emits exit; when that delayed old exit arrives, this unconditional cleanup stops the new timer, so periodic snapshots silently stop for the active browser. Tie cleanup to the PID check (or otherwise associate timer ownership) so an old process cannot clear state for a new one.

Useful? React with 👍 / 👎.

Comment thread src/browser/config.ts
Comment on lines +298 to +299
const sessionPersistenceEnabled = cfg?.sessionPersistence?.enabled !== false;
const sessionPersistenceIntervalMs = cfg?.sessionPersistence?.intervalMs ?? 60_000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Validate session persistence interval before using it

The config value is passed through without bounds checks, then fed into setInterval. In Node, out-of-range delays (for example 3000000000) overflow and are coerced to 1ms, which turns cookie persistence into a near-tight loop and can create heavy CDP/disk load. Clamp/sanitize sessionPersistence.intervalMs to a safe positive range when resolving config.

Useful? React with 👍 / 👎.

Comment thread src/browser/chrome.ts
Comment on lines +409 to +413
try {
const wsUrl = await getChromeWebSocketUrl(profile.cdpUrl);
if (wsUrl) {
const restored = await restoreBrowserState(wsUrl, userDataDir);
if (restored) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Honor disabled session persistence in startup restore path

Cookie restore now runs unconditionally at browser launch, so browser.sessionPersistence.enabled: false still loads persisted login state if the state file exists. That makes the opt-out ineffective for users who disable persistence (for privacy/debugging) and can produce unexpected re-auth behavior. Gate this launch restore behind the resolved session-persistence flag, consistent with the periodic saver toggle.

Useful? React with 👍 / 👎.

@steipete

Copy link
Copy Markdown
Contributor

Codex triage: still potentially useful, but this needs a rebase/narrow fix before review.

The feature maps directly to the Google Meet/login-session problem: managed openclaw browser sessions should survive gateway restarts so agents do not repeatedly hit Google login prompts.

Please keep the PR focused on managed-profile persistence and address the existing review blockers before it moves forward:

  • actually consume browser.sessionPersistence.enabled and intervalMs, or remove the config surface
  • prevent overlapping async saves from concurrent interval ticks
  • avoid misleading restored 0 cookies logging
  • rebase onto current browser/plugin code and rerun the current browser/session tests

This should complement #62288 rather than replace it: #45371 is managed profile persistence; #62288 is existing-session attach/fallback diagnostics.

@steipete steipete left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex deep review: still not mergeable in this shape.

The underlying problem is real and useful for Google Meet/login handoff: managed openclaw profile login state should survive gateway restarts. But this PR needs to be rebased and redesigned before it can land.

Blocking issues:

  1. The diff targets the old core browser layout (src/browser/*, src/config/types.browser.ts). Current main owns this in the bundled browser plugin (extensions/browser/src/browser/* plus generated config/schema docs). A merge/rebase needs to move the implementation to the plugin-owned surface and keep generated config metadata/docs aligned.

  2. The persistence format writes all browser cookies into a new plain JSON file inside the user data dir. That duplicates sensitive session cookies outside Chrome's normal cookie store protections. If we do this, the security model needs to be explicit: local-only managed profile, owner-readable file permissions, docs/security note, and no accidental exposure through profile reset/export/debug artifacts. Better still, first try graceful Chrome shutdown / profile flush or a narrower state strategy before creating an extra cookie vault.

  3. Network.getAllCookies/Network.setCookies is not obviously complete for modern Chrome state. Partitioned cookies/CHIPS, SameSite edge cases, source metadata, storage state beyond cookies, and Google account refresh state need live proof. The PR currently has mostly file-shape tests and one manual happy-path claim. Please add a focused integration/live smoke or at least a browser-control seam test that proves restore is actually called with representative CDP cookie fields.

  4. Runtime lifecycle has to be owned by the browser plugin lifecycle. Periodic timers must stop on profile reconcile, gateway reload, browser runtime shutdown, and failed launch paths. Current code was added to the old availability module and does not cover current plugin lifecycle.

Suggested path: rebase onto current main, keep this as managed-profile persistence only, add docs/security notes, and prove with a small live/manual checklist for Google/GitHub login persistence across gateway restart. This remains complementary to #62288; it does not solve existing-session attach diagnostics.

@steipete

Copy link
Copy Markdown
Contributor

Codex deep review: closing this PR rather than keeping it in the merge queue.

The underlying problem is understandable: managed browser profiles can lose login state if Chrome is repeatedly terminated before it flushes profile data. But this patch is no longer a good implementation for current main:

  • It edits the old src/browser/** layout. The active browser implementation is now under extensions/browser/src/browser/**, so the PR is stale/unmergeable without a rewrite.
  • The proposed fix snapshots all cookies through CDP into openclaw-saved-state.json. That creates a second plaintext cookie store outside Chrome's normal profile storage. For login/session persistence, that is a security regression and not the right default product behavior.
  • It only handles cookies, not the broader browser state sites rely on, and it adds a new config surface that needs docs/schema/runtime ownership.
  • Current main already uses a persistent OpenClaw userDataDir; the better fix is to improve clean shutdown/profile-lock handling and stop escalation where possible, not duplicate secrets into our own state file.

Best path if we revisit this: write a fresh current-path patch in extensions/browser/src/browser/chrome.ts / server-context.* that improves graceful shutdown and profile recovery, with regression tests around stopOpenClawChrome, profile lock cleanup, and restart behavior. Avoid CDP cookie export/import unless it is explicit, opt-in, documented, and treated as sensitive data.

Thanks for the investigation here; the root symptom is useful, but this implementation should not land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants