Skip to content

fix: clear 0.4.3 release blockers (discovery path/XDG + WebSocket close) - #1170

Closed
cursor[bot] wants to merge 21 commits into
mainfrom
cursor/fix-0.4.3-release-blockers
Closed

cursor[bot] wants to merge 21 commits into
mainfrom
cursor/fix-0.4.3-release-blockers

Conversation

@cursor

@cursor cursor Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

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

  1. Discovery path contract (feat: versioned metadata-only environment discovery (RFC 011) #1138 follow-up): reject C0/DEL/C1 control characters in RelativePath and generated Draft 2020-12 schemas so openenv catalog cannot forge output lines.
  2. Discovery cache trust (fix(discovery): per-user cache with trusted open/atomic save (from #1011) #1167 follow-up): ignore non-absolute XDG_CACHE_HOME values (XDG requires absolute paths) and add a planted-checkout regression so a relative cache dir in an untrusted tree cannot poison imports.
  3. WebSocket close lifecycle (fix(client): drop socket on receive timeout (cancel-safe close) #1162 follow-up): drain/shield dropped-socket closes before reconnect (avoids capacity/retry races), and guarantee current-socket/provider teardown when explicit close is cancelled.

Validation

  • Discovery/AutoEnv/generic-client: 214 passed, 13 expected skips
  • Catalog-contract: 117 passed
  • Ruff + usort clean on changed Python scope

Release impact

Until this lands (or the related merges are reverted), Thursday 0.4.3 publication should stay blocked.

Open in Web View Automation 

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 ~/.cache or a uid-scoped temp directory so a checkout cannot plant a cache that steers import_module. Regression tests cover relative XDG_CACHE_HOME and HOME.

EnvClient WebSocket lifecycle centralizes background closes via _schedule_socket_close / _best_effort_disconnect, drains pending closes before reconnect (avoiding single-session capacity races), and restructures _close_async with shielded drains and nested finally blocks 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.

dependabot Bot and others added 17 commits September 9, 2026 13:05
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>
@cursor
cursor Bot marked this pull request as ready for review September 16, 2026 06:04

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release-manager assessment (Wed 2026-09-16)

This is the reviewable vehicle for the three 0.4.3 release blockers already on main:

  1. #1138 path contract — reject C0/DEL/C1 in RelativePath + generated schemas (prevents forged openenv catalog lines).
  2. #1167 XDG trust — ignore non-absolute XDG_CACHE_HOME + planted-checkout regression.
  3. #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.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Release

@burtenshaw burtenshaw added bug Something isn't working size: medium Medium pull request labels Sep 16, 2026 — with Cursor
mock_connect.assert_not_called()

release_close.set()
await reconnect
Comment thread tests/test_core/test_generic_client.py Fixed

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread src/openenv/core/env_client.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and ruff check. The repo-wide lint.sh hook does fail, but only on pre-existing drift in files this PR does not touch — 57 envs//example files needing ruff format, plus the two usort-flagged files (tests/envs/test_grid_world.py, tests/envs/test_julia_env.py) already documented in AGENTS.md. None intersect this diff.
  • Debug code: CLEAN (this PR's scope). check-debug.sh reports print/TODO hits, but every one is in an untouched file (core/generic_client.py docstrings, cli/commands/{collect,serve}.py console.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.py257 passed, 5 skipped.

Open RFCs Context

  • RFC 011 — ARD-backed catalog discovery (Draft, @thegovind). Owns the 0.1-draft Environment 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.py implements; 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 three 0.1-draft schemas): model check (ord(c) < 0x20 or 0x7F <= ord(c) <= 0x9F) and schema ([\x00-\x1f\x7f-\x9f] in the pattern and a belt-and-suspenders not clause) 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 relative XDG_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 the gather, so caller cancellation cannot abandon in-flight close handshakes; _disconnect_async now detaches self._ws/self._ws_loop before the handshake (no stale socket cached on cancel); _close_async runs _disconnect_async in an inner finally so 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.py edits are infrastructure/orchestration-only. No agent-facing reset/step/state exposure, no client → server import, 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-draft schema; pre-1.0 Breaking-Change Policy.
  • The concern: RelativePath now 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_version stays 0.1-draft (no released contract changes), and it is documented in docs/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
Open in Web View Automation 

Sent by Cursor Automation: Pre-review

Comment on lines +168 to +170
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@bot-ci-comment

Copy link
Copy Markdown

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.

cursoragent and others added 2 commits September 16, 2026 06:19
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

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

View PR

Open in Web View Automation 

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

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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():

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f336c19. Configure here.

@burtenshaw burtenshaw closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size: medium Medium pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants