Retry PTY takeover attach when execd's eviction times out - #2840
Merged
Conversation
bxyu-nvidia
marked this pull request as ready for review
August 28, 2026 22:20
A takeover attach evicts the currently attached client, and execd waits for that client to acknowledge. When the old client's socket is half-open (silently dropped along the NLB/proxy path) it cannot answer, so execd's eviction times out and the attach comes back as a policy-violation close - surfaced as 'PTY session already has an attached client' even though the client passed takeover=True. The stale client is torn down in the background, so a fresh attempt lands. attach_pty now re-dials up to three times (2s/5s/10s) on exactly that signature; rejections without takeover stay definitive. Observed on terminal-bench 2.1 verify re-attaches at 1k-sandbox scale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Hemil Desai <hemild@nvidia.com>
The tb2.1 flow passes one PTY session through three holders (create, agent, verify), and every handoff relied on takeover-evicting the previous client. Eviction is only cheap while that client is alive; a socket idled through the whole agent phase is exactly the one the NLB/proxy path silently drops, turning the next takeover into a timeout. Two changes make handoffs clean instead of contested: - seed_session detaches its client right after creating the session, so the next attach finds nothing to evict; the server-side session keeps running and replays output on reattach. - every PTY dial requests websocket heartbeats (30s), so a dead socket surfaces as a failed ping and re-dials via the existing reattach path within a minute instead of dangling half-open. Takeover (with the retry from the previous commit) remains the recovery path for crashed holders rather than the routine handoff mechanism. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Hemil Desai <hemild@nvidia.com>
Signed-off-by: Brian Yu <bxyu@nvidia.com>
Signed-off-by: Brian Yu <bxyu@nvidia.com>
hemildesai
force-pushed
the
hemild/pty-takeover-retry
branch
from
August 28, 2026 23:01
ca8a5e1 to
5067394
Compare
bxyu-nvidia
approved these changes
Aug 28, 2026
Contributor
|
/ok to test 5067394 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On terminal-bench 2.1 runs at 1k-sandbox scale, verify-phase re-attaches intermittently fail with:
even though the attach passes
takeover=True.Root cause
The tb2.1 flow passes one PTY session through three holders — resources server (create) → opencode agent → resources server (verify) — and every handoff relies on takeover-evicting the previous client. execd implements takeover as a cooperative eviction with a timeout (its binary carries
takeover timed out for pty session). Eviction is only cheap while the previous client is alive: a socket idled through the whole agent phase is exactly the one the NLB/server-proxy path drops silently, so the next takeover waits out execd's eviction timeout and surfaces as a policy-violation close, which the client maps to "already has an attached client". The stale peer is torn down in the background, so a fresh attempt lands.Changes
provider.attach_pty): up to three re-dials (2s/5s/10s) on exactly that signature, fresh HTTP client per attempt. Non-takeover rejections and other errors stay definitive.tb2.1 seed_session): the creator detaches right after creating the session, so the agent's attach finds nothing to evict; the server-side session keeps running and replays on reattach. Takeover+retry becomes the recovery path for crashed holders, not the routine handoff.For the tb2.1 dev branch (#2175) — same contract, two snippets
The agent-attach and verify-reattach code paths live on the dev branch, so two spots there complete the pattern:
await pty_session.detach()— whoever is done with the terminal detaches; takeover is for crashes.attach(session_id=..., takeover=True)(it now retries), or reattach the stored session when it is merely detached.Testing
tests/unit_tests/test_opensandbox_pty.py— 61 passed (new: takeover-timeout retry lands on re-dial with the failed client released; no retry without takeover; every dial passes the heartbeat).$AGENT_MARK) was visible in verify — one server-side session across all three handoffs; exit code 0; sandbox deleted cleanly.Companion platform ask (separate): execd should force-close the stale socket instead of waiting for a half-open peer, and use a distinct close code for takeover-timeout vs genuinely-attached.
🤖 Generated with Claude Code