Why we built inactivity timeouts

Why we built inactivity timeouts

Jun 2, 2026

Jun 2, 2026

/

San Francisco

/

Dane Wilson

JunHyoung Ryu

JunHyoung Ryu

A Steel session can release itself when your agent goes quiet.

You set inactivityTimeout when you create a session, and if agent doesn't talk to the browser for that long, Steel shuts it down for you. So you stop paying for an idle browser when a run finishes or your agent quietly dies before it can clean up behind itself.

Apparently this is becoming a series. The last one was Why we built Agent Traces; this one is smaller.

One number was doing two jobs

Every session runs through a simple lifecycle, and the hard timeout you set is the longest it can stay Live before Steel releases it. Five minutes by default, up to 24 hours. Sessions are metered by the minute, so that single number ends up carrying a trade-off it was never meant to carry.

Set it short and a long task gets cut off mid-run. Set it long to be safe, and the failure flips: if your script crashes or you walk away, the browser stays Live and keeps metering until the full cap runs out. A 24-hour cap is fine right up until a dead job sits Live for 24 hours.

The honest answer was always to release the session yourself the moment you're done. And you should. But release() assumes your code reaches the release() call. Crashes don't. Hangs don't. Abandoned runs don't. The cases where you most want the session gone are the ones where your cleanup never runs.

So we built a floor that doesn't depend on your code getting there.

A shorter fuse under the cap

import Steel from 'steel-sdk';

const client = new Steel();

// Release the session after 1 minute with no CDP or input activity,
// capped at a 10-minute hard limit.
const session = await client.sessions.create({
  timeout: 600000,          // 10 minutes (hard cap)
  inactivityTimeout: 60000, // release after 1 minute of inactivity
});
import Steel from 'steel-sdk';

const client = new Steel();

// Release the session after 1 minute with no CDP or input activity,
// capped at a 10-minute hard limit.
const session = await client.sessions.create({
  timeout: 600000,          // 10 minutes (hard cap)
  inactivityTimeout: 60000, // release after 1 minute of inactivity
});
import Steel from 'steel-sdk';

const client = new Steel();

// Release the session after 1 minute with no CDP or input activity,
// capped at a 10-minute hard limit.
const session = await client.sessions.create({
  timeout: 600000,          // 10 minutes (hard cap)
  inactivityTimeout: 60000, // release after 1 minute of inactivity
});
import Steel from 'steel-sdk';

const client = new Steel();

// Release the session after 1 minute with no CDP or input activity,
// capped at a 10-minute hard limit.
const session = await client.sessions.create({
  timeout: 600000,          // 10 minutes (hard cap)
  inactivityTimeout: 60000, // release after 1 minute of inactivity
});

The session stays Live as long as you keep driving it, up to the 30-minute cap. Go quiet for 60 seconds and Steel releases it.

What counts as activity is deliberately simple: anything you send the browser resets the clock. CDP commands from Puppeteer or Playwright, clicks and typing through the live view. Each one starts the count over.

What doesn't count is the browser working on its own. A slow page load, or a scrape that's just sitting on the network, sends nothing. To the timer, nothing is silence. If a quiet stretch runs longer than your window, it trips mid-task. So set the window longer than the longest gap you expect between commands.

It's a floor, not a stopwatch

A few things to know before you lean on it. The hard timeout always wins, so inactivity only helps when it's the shorter of the two. It's off by default; omit it and nothing changes. And it's approximate: the check runs on the session's heartbeat, so expect "about a minute of silence," not an exact one. A session closed this way records its reason as inactivity_timeout, so later you can tell "we cleaned it up" apart from "it fell over."

None of this replaces releasing sessions yourself, which is still the cleanest way to stop the meter the instant you're done. Think of the hard timeout as how long a session may live, and inactivityTimeout as how long it may sit doing nothing before Steel assumes you've gone. It's a small safety net for the runs where your code never gets the chance.

Try it

It's live in the SDKs now. Pass inactivityTimeout to client.sessions.create() alongside your existing timeout and give a long-running job a short idle fuse.

Humans use Chrome. Agents use Steel.

Ready to

Build with Steel?

Ready to

Build with Steel?

Ready to

Build with Steel?

Ready to Build with Steel?

A better way to take your LLMs online.

© Steel · Inc. 2025.

All Systems Operational

Platform

Join the community