Back to Blog
Measurement · Essay

Solari cloud browser sessions die at ten minutes

Simon Doba·September 4, 2026·8 min read

Solari cloud browser sessions end about ten minutes after they start. There is no timeoutMs, no keep-alive call, no resume(), and no option that asks for a longer life. I measured six sessions: five died between 604 and 617 seconds from creation, one at 319. It made no difference whether the session sat idle, got pinged every 25 seconds, or streamed a CDP screencast at 14 frames a second. The idle one lived longest. expiresAt sits at creation plus five hours and is never reached.

I spent a day on that platform and came away with four packages. All four exist because of that one number.

What a ten-minute ceiling does to agent code

Any task longer than ten minutes fails, and it fails in a way that reads as your bug. The browser goes away mid-call and you get goto: Browser closed from a line that has nothing to do with the cause.

That is what outlive is for. It checkpoints cookies, localStorage and the URL, notices the death, launches a new browser, and calls your task again with attempt: 2.

  • 5 of 5 twelve-minute tasks completed. Baseline 0 of 5. Ten runs, fifteen browser sessions, the two arms concurrent and alternating: sign in with a real TOTP, then poll an account page every 20 seconds until 36 polls succeed. That is 720 seconds of sleeps against a session that lives around 600. Every baseline run reached poll 29 or 30 and threw.
  • Five relaunches, no failures. Median lost work 4.9 seconds per run, worst single death 15.0 seconds, at a 30 second checkpoint interval.
  • The overhead does not show in the wall clock. The outlive median run was 756 seconds. The baseline's own pace, 29 polls in 624 seconds, extrapolates to roughly 774 for 36. Surviving a death cost less than the run-to-run variation.
  • Replacing a browser takes 2121 ms. The checkpoint itself is 200 ms and 1640 bytes.

What survives a relaunch is cookies including the HttpOnly ones, localStorage, the URL, and a logged-in application session, measured 3 of 3. What does not survive is the DOM, in-page JavaScript state, scroll position, a half-filled form, IndexedDB, service workers and sessionStorage. Local variables inside your task function start again. A checkpoint is not a snapshot of a running program.

The other wall is a human one

An agent driving a browser hits things it cannot pass. A 2FA code it cannot know. A captcha it just failed. handraise lets it raise its hand: a QR appears in the terminal, the live session shows up on your phone, you fix the one thing, and the agent continues in the same session.

When the agent gets stuck, handraise creates a Solari sandbox, writes one zero-dependency Node script into it, and exposes it through port preview. Screencast frames go agent to relay to phone; taps and keys come back and get injected as CDP input events. The sandbox never touches the browser session. It exists to be a public address with a WebSocket on it, and it is destroyed when the handoff ends.

Measured against a TOTP wall, with a scripted human doing the typing:

  • 19 of 20 rescued. Baseline 0 of 20. What that measures is 20 runs of one workflow against a sign-in portal built for the benchmark, not 20 different real-world workflows, so it says nothing about a mixed blocker workload.
  • Stuck to live on the phone: 3.53 seconds at p50 over 30 runs, of which 2.68 is the relay cold start.
  • Input round trip 186 ms at p50 over 150 samples. That is phone to relay and back. The browser and CDP are not in that path, so a real tap costs this plus one more round trip. It is not tap-to-pixel latency and I will not quote it as one.
  • The one failure is the first problem again: the browser died mid-handoff at 3.6 minutes of session age.

The 5.5 second median handoff that shows up in my own notes is a scripted human typing at 60 ms a character with the cold start included. It is the machine floor of the handoff, not a measurement of how fast a person is.

A QR code is a bad interface if you are already in Slack

handraise-slack and handraise-telegram deliver the same interrupt where the person already is. A screenshot with two buttons arrives in the channel, and the click is the answer. No public endpoint, no webhook to host.

Those two are adapters rather than new ideas. The interrupt is one primitive, and where it surfaces should be swappable.

What I would tell you before you build on this

  • Find out how long a session lives before you design around it. timeoutMs is not a rolling idle window, and a keep-alive pinger does not extend anything.
  • Modelling a death as an outcome you can return made the rest of the code simpler than catching it as an exception would have.
  • Run your published artifact, not your source, under the runtime your users have. A green suite that has only ever seen your source is evidence about the wrong thing.

Next

outlive is at 0.1.0 and the measurements come from one region on one day, so treat the numbers as a snapshot rather than a distribution. I want to run the survival bench from a second region and see whether the ten minutes moves. If you have built on a cloud browser platform and know a session lifetime that is documented rather than measured, I would like to hear which one, because I did not find it.

blog.articles.solariSessionLifetime.footnote

Share this article

Building something similar?

I write about setups I actually use. If you're working on something comparable, I'd be curious what your workflow looks like.

Get in touch

Cookie Settings

We use cookies for analytics and to improve our website. Privacy policy