fix: clear 0.4.3 release blockers (discovery path/XDG + WebSocket close) - #1170
cursor[bot] wants to merge 21 commits into
Conversation
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.5.7 to 6.5.8. - [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst) - [Commits](tornadoweb/tornado@v6.5.7...v6.5.8) --- updated-dependencies: - dependency-name: tornado dependency-version: 6.5.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [nltk](https://github.com/nltk/nltk) from 3.9.4 to 3.10.3. - [Release notes](https://github.com/nltk/nltk/releases) - [Changelog](https://github.com/nltk/nltk/blob/develop/ChangeLog) - [Commits](nltk/nltk@3.9.4...v3.10.3) --- updated-dependencies: - dependency-name: nltk dependency-version: 3.10.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [pypdf](https://github.com/py-pdf/pypdf) from 6.14.2 to 6.16.1. - [Release notes](https://github.com/py-pdf/pypdf/releases) - [Changelog](https://github.com/py-pdf/pypdf/blob/main/CHANGELOG.md) - [Commits](py-pdf/pypdf@6.14.2...6.16.1) --- updated-dependencies: - dependency-name: pypdf dependency-version: 6.16.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
…s-safe-2026-09-09 Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
…s-safe-2026-09-09 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>
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
…s-safe-2026-09-09 Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
…s-safe-2026-09-09 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>
…s-safe-2026-09-09 Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
There was a problem hiding this comment.
Release-manager assessment (Wed 2026-09-16)
This is the reviewable vehicle for the three 0.4.3 release blockers already on main:
- #1138 path contract — reject C0/DEL/C1 in
RelativePath+ generated schemas (prevents forgedopenenv cataloglines). - #1167 XDG trust — ignore non-absolute
XDG_CACHE_HOME+ planted-checkout regression. - #1162 close lifecycle — drain dropped sockets before reconnect; guarantee teardown on cancelled explicit close.
Branch tip 2831ed5a is ahead of current main a3ee7644 with a 10-file effective diff (no env lockfile churn). Prior local validation on this tip: discovery/AutoEnv/generic-client 214 passed (13 skips), catalog-contract 117 passed, Ruff/usort clean.
Gates before merge: terminal exact-head CI, independent security review for the discovery/XDG surface (Darktex), and transport-owner review for the WebSocket close path (sergiopaniego / burtenshaw). Do not treat Bugbot alone as sufficient for this boundary.
Until this lands or the related merges are reverted, Thursday’s 0.4.3 publish stays blocked.
Sent by Cursor Automation: Release
| mock_connect.assert_not_called() | ||
|
|
||
| release_close.set() | ||
| await reconnect |
There was a problem hiding this comment.
Alignment Review Report
Two-tier alignment review of the 0.4.3 release-blocker fixes (discovery path/XDG hardening + WebSocket close lifecycle). Reviewed all 10 changed files against PRINCIPLES.md, INVARIANTS.md, and the open RFCs.
Automated Checks
- Lint: PASS (this PR's scope). All 6 changed Python files are clean under
ruff format --check,usort check, andruff check. The repo-widelint.shhook does fail, but only on pre-existing drift in files this PR does not touch — 57envs//example files needingruff format, plus the twousort-flagged files (tests/envs/test_grid_world.py,tests/envs/test_julia_env.py) already documented inAGENTS.md. None intersect this diff. - Debug code: CLEAN (this PR's scope).
check-debug.shreportsprint/TODOhits, but every one is in an untouched file (core/generic_client.pydocstrings,cli/commands/{collect,serve}.pyconsole.print, CLI templates). This diff introduces none; no debugger statements found. - Tests: PASS.
tests/discovery/test_catalog_contract.py,tests/envs/test_discovery.py,tests/test_core/test_generic_client.py→ 257 passed, 5 skipped.
Open RFCs Context
- RFC 011 — ARD-backed catalog discovery (Draft, @thegovind). Owns the
0.1-draftEnvironment Card schema and the relative-path profile this PR hardens. Directly relevant (see RFC Conflicts). - RFC 008 — Environment Auto-Validation (In Review). Referenced by RFC 011 for validation evidence; not touched here.
- RFC 002 — Framework Spec (In Review). Defines the WebSocket step loop that
env_client.pyimplements; this PR changes only private connection-lifecycle internals, so no public-API surface is affected.
Tier 1: Fixes Required
None. No lint failures, debug code, uninitialized variables, type errors, missing imports, syntax errors, or security regressions are introduced by this diff.
The three fixes are correct and well-covered:
- Discovery control-char rejection (
discovery/models.py+ the three0.1-draftschemas): model check (ord(c) < 0x20 or 0x7F <= ord(c) <= 0x9F) and schema ([\x00-\x1f\x7f-\x9f]in the pattern and a belt-and-suspendersnotclause) agree; the contract tests exercise the full C0/DEL/C1 set plus trailing-newline cases that previously slipped through. - XDG cache (
auto/_discovery.py):Path(base).is_absolute()guard correctly ignores relativeXDG_CACHE_HOME; empty-string short-circuits safely; regression test locks it in. - WebSocket lifecycle (
core/env_client.py):_drain_pending_close_tasks()filters to not-done tasks on the current loop and shields thegather, so caller cancellation cannot abandon in-flight close handshakes;_disconnect_asyncnow detachesself._ws/self._ws_loopbefore the handshake (no stale socket cached on cancel);_close_asyncruns_disconnect_asyncin an innerfinallyso provider/socket teardown survives cancellation during the drain. Both new async regression tests assert exactly these guarantees.
Tier 2: Alignment Discussion
Principle Conflicts
None identified.
env_client.pyedits are infrastructure/orchestration-only. No agent-facingreset/step/stateexposure, noclient → serverimport, no external reward computation — the agent-isolation, client-server-separation, and rewards-in-environment invariants all hold.- The discovery edits tighten validation of untrusted catalog metadata, strengthening (not weakening) the security posture in
INVARIANTS.md.
RFC Conflicts
None identified — the change aligns with the relevant open RFC.
ALIGNMENT FLAG (informational, non-blocking): validation tightening on a Draft wire type
- Principle/RFC at stake: RFC 011 (Draft) Environment Card
0.1-draftschema; pre-1.0 Breaking-Change Policy. - The concern:
RelativePathnow rejects inputs it previously accepted (e.g. trailing-newline paths, all C0/DEL/C1 controls). This is consistent with RFC 011 §8 (Security and failure behavior) — "Catalog descriptions, queries, tags, owner claims... are untrusted data. Display and model inputs must preserve that distinction" — and with the path profile RFC 011 defines (.= repo root; absolute paths and parent traversal invalid). It is a deliberate security fix,schema_versionstays0.1-draft(no released contract changes), and it is documented indocs/source/guides/catalog-discovery.md. Flagged only so the schema owner is aware that the accepted-input set narrowed. - Suggested reviewer: @thegovind (RFC 011 owner — confirm the hardened relative-path profile matches the intended card contract); @Darktex (security invariants author).
Summary
- 0 mechanical issues to fix (Tier 1)
- 1 alignment point for human review — informational confirmation that the Draft-schema hardening matches RFC 011's intended path contract (@thegovind)
- 0 RFC conflicts — the discovery change aligns with RFC 011 §8; plus 1 minor, non-blocking documentation-placement nit left inline
Sent by Cursor Automation: Pre-review
| The relative-path profile permits printable UTF-8 (including spaces), but | ||
| rejects C0, DEL, and C1 control characters so untrusted metadata cannot forge | ||
| CLI or log lines. |
There was a problem hiding this comment.
Minor nit (non-blocking): this paragraph describes what the packaged schemas enforce (relative-path safety), but it's inserted immediately after "Other rules require semantic validation:" — whose trailing colon introduces the table of semantic-validation rules right below it. As written, the schema-enforced note interrupts that lead-in → table flow.
Consider either folding it into the preceding sentence ("The packaged schemas enforce object shape, required fields, relative-path safety, ...") or separating it from the "semantic validation:" clause (e.g. its own line before that sentence), so the colon flows directly into the table.
…ase-blockers Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
…s' into cursor/openenv-release-management-86d8 Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
| await asyncio.wait_for(close_started.wait(), timeout=1) | ||
| disconnect.cancel() | ||
| with pytest.raises(asyncio.CancelledError): | ||
| await disconnect |
There was a problem hiding this comment.
Bugbot’s current-socket cancellation finding is fixed at 210b6781: the full notify/close operation is tracked before its first await, shielded from caller cancellation, and a red/green regression proves reconnect waits for the detached socket to release capacity.
Do not merge this PR, though. Concurrent draft #1172 is the complete replacement: it also carries the child-close cancellation and relative-HOME cache fallback fixes that this head omits. #1172 itself remains release-blocked on rejecting U+2028/U+2029 line separators plus fresh security/transport-owner approval. Keep #1170 only as superseded evidence until #1172 is accepted.
Sent by Cursor Automation: Release
Keep parent WebSocket/provider cleanup running when child.close is cancelled, and refuse relative HOME when selecting the discovery cache so an untrusted checkout cannot plant a victim-owned cache path. Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
| assert not pending.cancelled() | ||
|
|
||
| release_close.set() | ||
| await pending |
| await child_started.wait() | ||
| close_task.cancel() | ||
| with pytest.raises(asyncio.CancelledError): | ||
| await close_task |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f336c19. Configure here.
| # cannot be turned into a fixed, cross-user planting target. | ||
| uid = os.getuid() if hasattr(os, "getuid") else os.getpid() | ||
| root = Path(tempfile.gettempdir()) / f"openenv-{uid}-cache" | ||
| if not root.is_absolute(): |
There was a problem hiding this comment.
Temp fallback can use checkout cache
High Severity
The new relative-HOME fallback builds the discovery cache path from tempfile.gettempdir(), which turns a relative TMPDIR into a cwd-absolute path and can fall back to the working directory. An untrusted checkout that sets both HOME and TMPDIR can still supply a victim-owned planted cache, and later import_module follows that cached client_module_path. The is_absolute() guard does not catch this because gettempdir() already returns an absolute path.
Reviewed by Cursor Bugbot for commit f336c19. Configure here.
| await child.close() | ||
| finally: | ||
| # Parent teardown must run even when a child close is cancelled. | ||
| self._child_clients.clear() |
There was a problem hiding this comment.
Cancel drops remaining child sessions
Medium Severity
Cancelling _close_async during one child.close() now clears _child_clients before later sessions are closed. Those children are forgotten, so a retry cannot finish their WebSocket teardown and they can keep occupying server capacity. The new test only covers a single child.
Reviewed by Cursor Bugbot for commit f336c19. Configure here.




Summary
Release-blocker forward-fix for Thursday's 0.4.3 candidate on current
main.This content previously lived on
cursor/dependabot-envs-safe-2026-09-09, which GitHub still associates with merged #1136, so this PR uses a fresh branch (cursor/fix-0.4.3-release-blockers) for review.Included fixes
RelativePathand generated Draft 2020-12 schemas soopenenv catalogcannot forge output lines.XDG_CACHE_HOMEvalues (XDG requires absolute paths) and add a planted-checkout regression so a relative cache dir in an untrusted tree cannot poison imports.Validation
Release impact
Until this lands (or the related merges are reverted), Thursday 0.4.3 publication should stay blocked.
Note
Medium Risk
Changes security-sensitive discovery cache resolution and core WebSocket connect/close/reconnect behavior; behavior is heavily regression-tested but affects all environment clients.
Overview
Addresses three 0.4.3 release blockers: catalog path safety, discovery cache trust, and environment client WebSocket teardown.
Catalog relative paths now reject C0, DEL, and C1 control characters (not just NUL) in Pydantic
relative_path, matching Draft 2020-12 schema patterns and docs; printable UTF-8 including spaces remains allowed. Contract tests cover the full ASCII control range.Auto-discovery cache only honors absolute
XDG_CACHE_HOME; relative values fall back to an absolute~/.cacheor a uid-scoped temp directory so a checkout cannot plant a cache that steersimport_module. Regression tests cover relativeXDG_CACHE_HOMEandHOME.EnvClientWebSocket lifecycle centralizes background closes via_schedule_socket_close/_best_effort_disconnect, drains pending closes before reconnect (avoiding single-session capacity races), and restructures_close_asyncwith shielded drains and nestedfinallyblocks so cancellation during child close or pending-close wait still disconnects the current socket and stops the provider.Reviewed by Cursor Bugbot for commit f336c19. Bugbot is set up for automated code reviews on this repo. Configure here.