Overview
- A proxy session is one browser, with its tabs and cookies, living in the
proxy.
chromium.launch()starts one (the MCP server starts one on the firstopen_page). - When the session ends, the browser, tabs and cookies are gone. There is nothing to reconnect to.
- A brief network drop does not end it. By default the SDK reconnects to the same session. See Resume after a dropped connection.
- Mock tokens do not expire. Only the session does.
- Every call carries a deadline, so one hung call never leaves your process waiting forever.
Session limits
A call in flight counts as activity. While the proxy is still working on a
call, it keeps resetting the idle timer, and the answer resets it once more. So a
single call that runs longer than 15 minutes, such as a fill waiting on the
account owner, is not ended by
idle_timeout. It can still end at its own
deadline (see Proxy side) or at the 60 minute hard limit.
A session can outlive one of your turns. The proxy sets no per-turn limit, only
these two timers. See Driving a session across turns.
Terminal and non-terminal errors
Four codes mean this session is over. They do not mean “retry this call”.
A close with code 4001 arrives as
unauthorized instead.
Recovery for all four is a fresh chromium.launch(). Before replaying anything,
apply the unknown-outcome rule: none of these
codes says whether the last action ran.
Three codes look like a dead connection and are not terminal:
None of the three says whether the call ran. Treat an action as possibly
landed, and check before you repeat it. After
unanswered or action_timeout
the call may still be running, so follow
Recover from unanswered.
Relaunching after any of them throws away a session that was still there. If
you hold a browser across calls, check it before reusing it:
The deadlines
Client side
The SDK gives each call a deadline. Past it, the call rejects withrequest_timeout and the session closes. requestTimeoutMs(method, params)
returns the value for any call.
No deadline is longer than 65 minutes, so the session’s 60-minute hard timeout
always ends a call first.
The per-action bound
The deadline above answers “is the connection dead?”, so it is generous and it ends the session when it fires. To give up on one hung click sooner and keep the session, set a hard bound per call:SELF_AGENT_PAY_ACTION_TIMEOUT_MS. Unset means no per-action bound.
- A call that runs past it rejects with
action_timeout. The session stays open, unlikerequest_timeout. - A
timeoutyou pass on the call does not raise it. - It applies only when it is shorter than the call’s own deadline.
- It never applies to
browser.close,requestChallengeHandoff,attestedCheckout,selectOption, or anyfill,typeorpressSequentially. Those can be waiting on the account owner, and the proxy would carry on after you gave up.
unanswered.
Proxy side
The proxy keeps its own, shorter deadline on each page call and answersunanswered when Playwright never comes back: 120 seconds, or the call’s own
timeout plus 30 seconds, capped at an hour. That deadline is suspended while a
call is waiting on the account owner.
A few calls keep no proxy deadline: browser.close, attestedCheckout,
requestChallengeHandoff, recordOutcome, and any call passed
{ timeout: 0 }. For those, a stall ends in request_timeout or the hard timer,
both terminal.
Recover from unanswered
The stalled call was abandoned, not cancelled. It keeps running in the proxy
with its result dropped, and an action may still land later.
- A read, wait or count
- An action
Retry once. The session is intact and the call changed nothing.
Resume after a dropped connection
When the network or a load balancer drops the socket, the SDK redials and takes the same session back. It keeps trying for about 30 seconds, or up to 150 seconds while the proxy is still letting go of the old connection. The browser, its pages, its cookies and its fill state are all still there. Calls you make while it redials wait for it. The proxy holds a dropped session for 60 seconds. A client that has not come back by then finds it closed. Only a call that was in flight across the drop fails, withconnection_resumed. Its answer was lost with the socket, so its outcome is
unknown:
- A read, wait or count: retry it.
- An action: it may have landed. Look at the page on the same browser before repeating it. Do not relaunch.
connection_closed instead. err.dropReason says
why the connection ended: transport_lost, proxy_restart, proxy_error,
superseded or resume_refused.
Resume is on by default. To end the session on any drop instead, pass
chromium.launch({ resume: false }) or set SELF_AGENT_PAY_RESUME=0. A proxy
that does not support resume ends the session on a drop either way.
When an outcome is unknown
A missing or connection-level error code means the request got no verdict, not that it never landed. Any close rejects every in-flight call, and the connection can drop after a click or submit reached the merchant. Arequest_timeout on a card fill does not say whether the fill happened, so
a blind retry can pay twice. Check the merchant’s order state, or the
transactions and activity feed on your dashboard. The activity feed is best
effort, so a missing entry there does not prove the fill did not happen.
browser.outcome() cannot settle it
Its confirmationDetected flag is set only by your own page.recordOutcome()
call. The proxy never sets it by observing the merchant.
confirmationDetected: truemeans your agent calledrecordOutcome(), nothing more. The proxy does not check it against the page. It is only as reliable as the check your agent made first, so confirm against merchant state before you report the order.confirmationDetected: falseis inconclusive. An order that succeeded a moment before the failure looks exactly like one that never started.
outcome() is not available at all: it runs over
that socket, and a fresh session reports its own empty outcome. The only sound
check is durable merchant state.
Driving a session across turns
A browser session lives as long as the process that owns it. Nothing requires launch, act and close to sit in one script. To drive a checkout one step at a time, keep one process running that holds the browser and takes commands, for example from a command file it polls.pkill -f driver also matches the shell command that launched it. A driver that
dies seconds after starting was most likely killed by its own launcher.Related
Error codes
Every code, and whether retrying is safe.
Confirming outcomes
Session outcome, run report, and what counts as proof.