revert(tbench2): disable unsafe idle-session reaper - #1185
Conversation
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
…e-management-0b68 Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
There was a problem hiding this comment.
Alignment Review Report
Heads-up: this PR was squash-merged to main as 05217002 (~11:35 UTC) — its head tree is byte-identical to current origin/main. Posting for the record; the one actionable item below is a core follow-up, not a change to this PR.
Automated Checks
- Lint: PASS on the changed file —
ruff format --check✓ andruff check✓ forenvs/tbench2_env/server/app.py.usortwould reorder the standalone-import fallback block, but that is pre-existing (base45ad8aefails identically) andenvs/**is outside both CI and the hook's usort scope, so nothing is introduced or enforced here. - Debug code: CLEAN — no prints/breakpoints added (
check-debug.shscanssrc/only; this is anenvs/change). - Tests: 26 passed, 1 skipped (
tests/envs/test_tbench2_env.py), matching the PR's test plan. Import smoke (from tbench2_env.server.app import app) succeeds in bothTB2_MODE=localandTB2_MODE=docker.
Revert correctness (verified)
create_app(..., max_concurrent_envs=max_concurrent)is valid API usage —create_appacceptsmax_concurrent_envsdirectly (http_server.py:1770) and internally buildsConcurrencyConfig(..., session_timeout=None), so the reaper becomes a no-op.- Revert is complete: zero residual
ConcurrencyConfig/session_timeout/SESSION_IDLE_TIMEOUT_Sreferences anywhere underenvs/tbench2_env/(grep clean); imports + docstring cleaned up. - The two
style(tbench2): ...commits cancel out (blank lines removed then re-added), so the net diff is only the reaper removal.
Open RFCs Context
- RFC 005 – Agentic Harnesses (In Review), L622-623: "Delays between turns are transparent... there is no concept of 'idle time'"; its
session_timeout_sis a per-turn wall-clock cap. - RFC 002 – Env Spec (In Review), Cloud-Sandbox amendment S6 / L198: "idle timeouts can drop a live transport mid-episode, so providers choose conservative defaults for RL rollouts."
- No RFC specifies the core
ConcurrencyConfig.session_timeoutidle-reaper behavior itself.
Tier 1: Fixes Required
- None. (Optional / non-blocking / out-of-CI-scope: the standalone-import fallback block isn't usort-ordered — pre-existing; the
openenvthird-party import should precede themodels/serverfirst-party imports if you ever wantenvs/usort-clean.)
Tier 2: Alignment Discussion
Principle Conflicts
None. The revert trades the reaper's slot-reclamation ("Production-readiness") for client correctness, which is the right call given the failure mode below. No invariant is touched (Gym API signatures, agent isolation, client-server separation, rewards-in-env, credential handling all unaffected).
RFC Conflicts
ALIGNMENT FLAG: The "unsafe" behavior lives in core, not tbench2 — the reaper destroys live WS-owned sessions
- Principle/RFC at stake: RFC 005 L622-623 ("no concept of idle time / delays between turns are transparent"); RFC 002 S6 / L198 ("idle timeouts can drop a live transport mid-episode"); PRINCIPLES "Production-readiness".
- The concern:
http_server.py::_reap_idle_sessionsonly exempts sessions in_session_websocket_attachments(L530). A default/wsconnection takes theowns_session=Truebranch (L1558-1559) and is never added to that set, so once idle pastsession_timeoutthe reaper calls_destroy_session(L547) and tears down the env/container while the handler still holdssession_env(captured L1558) blocked atreceive_text()(L1578) → a slow client resumes against a dead env and gets an opaque error; half-open handlers can stay blocked. That is exactly the PR's rationale. tbench2 was the sole opt-in (#1000); this revert removes its exposure, but the core bug stays latent for any futuresession_timeoutuser. Per the PR's own future-work: re-enable only after core tracks active WebSockets, refreshes activity on/wstraffic, and invalidates/terminates the handler before env cleanup. - Minor (naming): core
ConcurrencyConfig.session_timeout(idle-reap) vs RFC 005HarnessConfig.session_timeout_s(per-turn wall-clock) — same-ish name, different meaning; worth reconciling when the core fix lands. - Suggested reviewer: git blame
swappy(reaper author) +Sachin(WS-session/attachment author) + @Darktex (session-lifecycle & dual-API invariants); cc @Shi-Dong (#1000 author) and @burtenshaw (env owner / revert co-author).
Summary
- 0 mechanical issues to fix — revert is clean, complete, and tested; both import modes construct successfully.
- 1 alignment point for human review — the underlying reaper bug is in core
http_server.pyand remains for future opt-ins; track a proper core fix before re-enablingsession_timeoutanywhere. - 1 RFC alignment note (positive) — this revert is consistent with RFC 005's "no idle-time" model and RFC 002's idle-timeout warning.
Sent by Cursor Automation: Pre-review
| max_concurrent_envs=max_concurrent, | ||
| session_timeout=session_idle_timeout, | ||
| ), | ||
| max_concurrent_envs=max_concurrent, |
There was a problem hiding this comment.
Revert is correct. create_app accepts max_concurrent_envs directly (http_server.py:1770) and defaults session_timeout=None, so the idle reaper becomes a no-op — verified the app constructs in both TB2_MODE=local and docker, and tests/envs/test_tbench2_env.py is 26 passed / 1 skipped.
The root cause is in core http_server.py: a default /ws session takes the owns_session=True path (L1558-1559) and is never added to _session_websocket_attachments, so _reap_idle_sessions (L530/L547) can destroy it mid-flight while this handler still holds session_env. Please keep session_timeout off for tbench2 until core tracks the live WebSocket and invalidates the handler before teardown.


Summary
Reverts #1000 until core session destruction can terminate or invalidate its live WebSocket handler. The enabled reaper currently destroys the environment/container while the handler retains
session_env, so a slow client can continue against a closed environment and receive an opaque error; half-open handlers can also remain blocked.Type of Change
Alignment Checklist
Before submitting, verify:
.claude/docs/PRINCIPLES.mdand this PR aligns with our principles.claude/docs/INVARIANTS.mdand no invariants are violatedRFC Status
Test Plan
PYTHONPATH=src:envs .venv/bin/python -m pytest tests/envs/test_tbench2_env.py -q— 26 passed, 1 skipped.venv/bin/python -m ruff format --check envs/tbench2_env/server/app.py.venv/bin/python -m ruff check envs/tbench2_env/server/app.pyfrom tbench2_env.server.app import appClaude Code Review
The one-file rollback restores the pre-#1000 configuration and is conflict-free on current main. A future core fix should track active WebSockets, refresh MCP-session activity, send a defined termination signal before environment cleanup, and prove half-open handler exit before re-enabling
session_timeoutfor TBench2.Note
Medium Risk
Disabling idle reaping can leave half-open WebSocket sessions occupying concurrency slots longer, but re-enabling timeout without core WebSocket teardown was judged worse for client correctness.
Overview
Rolls back TBench2’s idle-session reaper by stopping use of
ConcurrencyConfigandSESSION_IDLE_TIMEOUT_Sinenvs/tbench2_env/server/app.py.create_appis wired again with onlymax_concurrent_envs(fromMAX_CONCURRENT_ENVS); docs no longer mention session idle timeout. This matches a revert of the behavior that destroyed environments/containers on idle while WebSocket handlers could still holdsession_env.Reviewed by Cursor Bugbot for commit 2c74c7a. Bugbot is set up for automated code reviews on this repo. Configure here.