Thanks to visit codestin.com
Credit goes to github.com

Skip to content

E2-11: nightly live matrix for the managed Environment builders - #32

Merged
echarles merged 9 commits into
mainfrom
feat/environments-live-matrix
Sep 14, 2026
Merged

echarles merged 9 commits into
mainfrom
feat/environments-live-matrix

Conversation

@echarles

Copy link
Copy Markdown
Member

What

E2-11: a nightly matrix that builds a real artifact on each real managed
Environment provider (daytona, e2b, modal) from a real,
hash-verified lock
— not the trivial ones every earlier live drill in
this plan used, which is exactly what hid E2-04's build-time doctor bug
until this test built one for real — launches a sandbox from it, and runs
the formal core tier (conformance.run_core_tier) against it. Everything
it creates is deleted, whether the run passes or not.

  • tests/test_environment_live_matrix.py — the test itself, pytest.mark.live, gated on CODE_SANDBOXES_LIVE=1 like the existing live suites.
  • Makefile — make environments-live, mirroring the existing live-matrix/kill-relaunch targets.
  • .github/workflows/environments-live.yml — runs it nightly (04:17 UTC) and on workflow_dispatch, and opens (or comments on) an issue naming the provider and the check when a genuine, non-xfail failure occurs.

Cross-branch dependency (read this before merging)

E2-03 (#29, e2b), E2-04 (#30, daytona) and E2-05 (#31, modal) are each
still their own open PR. This branch is deliberately cut from main, not
from any of them, so it needs no change of its own as each merges — it
starts working per-variant the moment get_builder actually builds for
that variant, and today, against unmerged main, it correctly fails
outright (DL_ENV_CAPABILITY_UNSUPPORTED, "has not landed") rather than
xfail, since that's the true state of main, not a defect this test is
expecting. Once #29/#30/#31 are all merged this will need no further
change to start exercising all three for real.

e2b and modal are @pytest.mark.xfail(strict=False) for their own
already-documented, unticked gaps:

  • E2-03: code-interpreter-v1 lands datalayer on uid 1001/gid 1001, not the contract's 1000:100.
  • E2-05: modal_sandbox.py has no setpriv wrapper yet, so it execs as root today.

strict=False means an unexpected pass (XPASS) doesn't fail the run
either — it's the positive signal that one of those gaps has closed, for
its own plan box to tick. daytona carries no such marker: E2-04's own
live drill already proved it passes the core tier in full, so a failure
there is a real regression.

Two real bugs this test found (already fixed, elsewhere)

Building this test against a real, non-trivial resolved lock (rather
than the toy ones prior live drills used) surfaced two genuine defects,
both already fixed on their own PRs:

  1. Daytona's build-time doctor check is architecturally incapable of passing — a RUN step's PID1 during a Daytona build is the build agent's, not the final snapshot's, so datalayer-sandbox doctor's init row fails deterministically. Fixed in environments: the Daytona builder — build, inspect, exists (E2-04) #30 by removing the doctor check from build() (doctor still runs fine post-launch).
  2. Every Modal-launched environment was dying on real launch — modal_sandbox.py's ModalSandbox.start() calls Sandbox.create(**create_kwargs) with zero positional command args, so an entrypoint relying on exec "$@" alone was a no-op and the container exited immediately. Fixed in environments: the Modal builder — build, inspect, exists (E2-05) #31 by adding a sleep infinity fallback when no command is given.

Still needed before the nightly run does anything for real

The workflow needs six repository secrets, which I can't add myself:
E2B_API_KEY, DAYTONA_API_KEY, MODAL_TOKEN_ID, MODAL_TOKEN_SECRET,
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY (the AWS pair scoped to read
the approved base from ECR only, per D-18). Until they're added, the
scheduled run will execute and every provider's test will skip by name
rather than fail — this is not itself something the workflow flags as
broken.

Verification

tests/test_environment_live_matrix.py builds a real artifact on each real
provider (daytona, e2b, modal) from a real, hash-verified lock (not the
trivial ones every earlier live drill in this plan used, which is exactly
what hid E2-04's build-time doctor bug until this test used a real one),
launches a sandbox from it, and runs the formal core tier
(conformance.run_core_tier) against it. Everything it creates is deleted,
pass or fail.

Opened from origin/main on purpose: E2-03/E2-04/E2-05 are each still their
own open PR, so this needs no change of its own as each merges — it starts
working per-variant the moment get_builder actually builds for it, and
fails outright (not xfail) against an unmerged variant today, correctly,
since that is the true state of main.

E2B and Modal are xfail(strict=False) for their own already-documented,
unticked gaps (E2-03's uid/gid mismatch; modal_sandbox.py's missing
setpriv, so it execs as root). Daytona carries no such marker: E2-04's own
live drill already proved it passes in full, so a failure there is a real
regression.

.github/workflows/environments-live.yml runs this nightly (04:17 UTC) plus
on workflow_dispatch, and opens (or comments on) an issue naming the
provider and the check when a genuine, non-xfail failure occurs — parsed
straight out of the JUnit report's failure/error nodes, which an xfail
never populates. Needs six repository secrets the owner still has to add:
E2B_API_KEY, DAYTONA_API_KEY, MODAL_TOKEN_ID, MODAL_TOKEN_SECRET,
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY.

Makefile gets an `environments-live` target mirroring the existing
`live-matrix`/`kill-relaunch` pattern.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Copilot AI lite review requested due to automatic review settings September 13, 2026 16:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Moderate unresolved issues affect workflow reliability and resource cleanup.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a nightly live matrix that builds, launches, and validates managed Environment artifacts across Daytona, E2B, and Modal.

Changes:

  • Adds provider live tests and core-tier validation.
  • Adds make environments-live.
  • Adds scheduled/manual workflow with failure issue reporting.

Review findings: eight moderate issues remain, covering workflow permissions and timeout, warning filters, build ID collisions, partial-start cleanup, exception preservation, provider-specific base resolution, and Daytona build-context cleanup.

File summaries
File Description
tests/test_environment_live_matrix.py Builds, launches, validates, and cleans up provider artifacts.
Makefile Adds the live matrix command.
.github/workflows/environments-live.yml Runs the matrix nightly and reports failures.
Review details

Suppressed comments (13)

.github/workflows/environments-live.yml:34

  • The three tests run sequentially, but the provider builders allow up to 30 minutes for Daytona, 30 minutes for Modal, and 20 minutes for E2B, before resolution and launch time. A valid slow run can hit this 45-minute hard limit while a provider is still active, skipping cleanup and preventing later providers from running. Increase the timeout or split this into per-provider jobs.
    timeout-minutes: 45

tests/test_environment_live_matrix.py:55

  • Pytest is configured with filterwarnings = ["error", ...] (pyproject.toml:95-106), and the other live suites explicitly ignore provider SDK deprecations. This module imports Modal/Daytona/E2B SDKs without those marks, so a client warning can fail a row before any sandbox check; add the live warning filters here.
pytestmark = [pytest.mark.live]

tests/test_environment_live_matrix.py:181

  • Because this module-scoped fixture is set up before the test body, _resolved_lock() runs before _skip_unless_available(). When live mode or provider/AWS credentials are missing, the workflow still performs uv/PyPI resolution (and can fail) instead of skipping immediately as documented. Gate the fixture before resolving, or resolve the lock only after the per-provider availability check.
    lock_text, resolved_bases = _resolved_lock()

tests/test_environment_live_matrix.py:155

  • BuildRequest.lock_digest is documented as the digest of lock_text, but this passes an all-zero placeholder rather than resolve_environment()'s returned digest. Any builder metadata or attestation that uses this field will advertise a hash that cannot verify the lock installed by this test; carry the resolver's digest through the fixture.
        lock_digest="sha256:" + "00" * 32,

tests/test_environment_live_matrix.py:226

  • This xfail covers the entire test body, so provider outages, bad credentials, SDK/import failures, and build/launch regressions are all reported as expected failures—not just the documented root-identity gap. The nightly workflow will therefore miss genuine Modal failures; verify the expected check IDs first and only xfail that specific outcome.
    @pytest.mark.xfail(
        reason="modal_sandbox.py has no setpriv wrapper yet: execs run as root, not "
        "the contract's 1000:100 — checks 1, 2, 5 and 6 all fail on it",
        strict=False,

tests/test_environment_live_matrix.py:264

  • This blanket marker also turns get_builder("e2b")/build() failures such as the current DL_ENV_CAPABILITY_UNSUPPORTED "has not landed" state into xfails, contrary to the module's stated expectation that an unmerged builder fails outright. It likewise hides unrelated provider regressions; restrict xfail to the verified uid/gid failure and let other exceptions fail the job.
    @pytest.mark.xfail(
        reason="E2-03's own documented gap: code-interpreter-v1 lands datalayer "
        "on uid 1001, gid 1001, not the contract's 1000:100",
        strict=False,

tests/test_environment_live_matrix.py:283

  • This finally is a no-op, so every successful E2B run leaves its remote template behind; if build() fails before artifact is assigned, this block is not reached at all. The nightly job will accumulate provider artifacts and costs, contradicting the module's cleanup guarantee. Add provider-side deletion (for example through the provider API) or do not schedule this row until cleanup is available.
        finally:
            pass  # E2B's SDK exposes no template delete call (found live, E2-03).

tests/test_environment_live_matrix.py:244

  • ModalSandbox.start() creates the remote sandbox before _started is set, and stop() is a no-op when startup fails before that flag is set. Thus an exception during driver/context setup can leave a Modal sandbox running because this cleanup is only entered after start() returns. Make partial-start cleanup explicit in the provider lifecycle.
            sandbox.start()

tests/test_environment_live_matrix.py:277

  • E2BSandbox.start() creates the remote sandbox before setting _started, while stop() returns when _started is false. If context setup fails after creation, this cleanup is skipped and the live matrix leaks the sandbox. Make partial-start cleanup explicit in the provider lifecycle rather than relying on start() to complete.
            sandbox.start()

tests/test_environment_live_matrix.py:252

  • Deleting only artifact.provider_artifact_id is not sufficient for Modal: its chained image build leaves intermediate layer images that the final image deletion does not remove. Each nightly build will therefore accumulate Modal artifacts despite the module's cleanup guarantee; enumerate the intermediate ids through the provider cleanup/reconciliation path as well.
            modal.experimental.image_delete(
                artifact.provider_artifact_id, client=modal.Client.from_env()
            )

tests/test_environment_live_matrix.py:209

  • The artifact delete in finally is unguarded, so a provider cleanup error replaces the original build/core-tier exception and the nightly issue loses the actual check failure. Preserve the primary exception while recording cleanup failure separately.
            import daytona

            daytona.Daytona().snapshot.delete(artifact.provider_artifact_id)

tests/test_environment_live_matrix.py:125

  • The lock is resolved with variants=['daytona'], then the Daytona base is reused for E2B and Modal. resolve_environment returns a per-variant base map, so this matrix will build the wrong base or fail to exercise provider-specific base pins as soon as those variants diverge. Resolve all three variants and select resolved_bases[variant] per test.
    outcome = resolve_environment(
        spec=_ENVIRONMENT_SPEC, variants=["daytona"], runner=LocalResolveRunner()
    )
    return outcome["content"], outcome["resolved_bases"]

tests/test_environment_live_matrix.py:209

  • snapshot.delete(...) removes the snapshot id, but Daytona keeps the build context in provider object storage and this path has no cleanup for it. The nightly test therefore accumulates remote build data even when snapshot deletion succeeds, contradicting the claim that everything it creates is deleted. Add provider-specific context cleanup or document and bound the retained resource.
            import daytona

            daytona.Daytona().snapshot.delete(artifact.provider_artifact_id)
  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

# 04:17 UTC: off-peak for every provider this reaches, and away from the
# hour boundary every other scheduled workflow in this org tends to pick.
- cron: '17 4 * * *'
workflow_dispatch: {}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in dc8c811: added permissions: contents: read, issues: write at the workflow level.

Comment thread tests/test_environment_live_matrix.py Outdated
return BuildRequest(
environment_uid="01k0env0000000000000000000",
version=1,
build_uid=f"live-{variant}",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in dc8c811: build_uid is now f"live-{variant}-{uuid.uuid4().hex[:8]}" — a per-run nonce, provider prefix kept.

try:
assert builder.exists(artifact)
sandbox = Sandbox.create(variant="daytona", artifact=artifact, timeout=600)
sandbox.start()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Real bug, confirmed, and not unique to Daytona — the same shape exists in modal_sandbox.py and e2b_sandbox.py too: start() creates the remote resource well before it marks itself started (create_context/_start_driver, building SandboxInfo all come after), and stop() was guarded on _started rather than on the resource itself.

Fixed in dc8c811 in all three, identically: stop() now returns early only when self._sandbox is None — the null-check each already had internally — so a failure between creation and _started = True still gets cleaned up. _started's meaning is unchanged everywhere else it's checked (still gates whether the sandbox is ready to use, e.g. run_code/interrupt). A regression test per file (test_daytona.py, test_e2b.py, test_modal_google_colab_sandbox.py) confirms cleanup now runs even when _started was never set, and that stop() stays a true no-op before anything was ever created.

echarles and others added 8 commits September 13, 2026 18:39
…real orphan-on-partial-start bug

Three findings, all fixed:

- environments-live.yml declared no `permissions`, so on the default
  read-only token a genuine matrix failure would 403 trying to label and
  open the very issue meant to report it. Added `contents: read`,
  `issues: write`.

- `_build_request`'s `build_uid` was constant across invocations
  (`f"live-{variant}"`). E2B and Daytona both fold `build_uid` into the
  artifact/template name, so a second nightly run would collide with the
  first's own artifact instead of building a fresh one. Now a per-run
  nonce, provider prefix kept.

- A real, pre-existing bug in all three sandbox launchers, not introduced
  by this PR but exposed by it: `start()` creates the remote sandbox well
  before it marks itself started (`create_context`/`_start_driver`,
  building `SandboxInfo` all come after), while `stop()` was guarded on
  `_started` rather than on the resource itself — a failure in between
  left a real, running sandbox that `stop()` skipped entirely, orphaned
  for good. Fixed identically in `daytona_sandbox.py`, `modal_sandbox.py`
  and `e2b_sandbox.py`: `stop()` now checks `self._sandbox is None`, which
  was already the inner null-check every one of them had; nothing about
  `_started`'s meaning anywhere else changes. A regression test per file
  confirms cleanup now runs even when `_started` was never set, and that
  `stop()` is still a true no-op before anything was ever created.

ruff/mypy clean; the four affected test files all pass (line counts:
daytona 56, e2b 31, modal/colab 16, plus the live-matrix file's own
unit-checkable parts).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
…hat identity is fixed

feat/modal-builder (PR #31) closed the identity gap this file's own xfail
reason named: a contract-built artifact's ModalSandbox now drops its
driver to 1000:100, gated on image_id so a plain ModalSandbox is
unaffected. Checks 1/2 (doctor, identity) pass live now; checks 5/6
(imports, filesystem) still fail, confirmed a separate, pre-existing issue
unrelated to identity -- a plain, non-Environments ModalSandbox has no
trouble over the same span. Updates the xfail reason and class docstring
to describe the current, accurate picture rather than the superseded one.

Text-only change: this branch's own adapters/modal.py and
modal_sandbox.py are still the pre-E2-05 stubs (this branch is cut from
main, not from feat/modal-builder), so nothing here is runnable proof --
it just keeps this file honest about what to expect once #31 merges.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
…ule docstring

E2-03's own fixes (feat/e2b-builder) mean the build no longer refuses at
build time -- it succeeds in full. The gap moved to launch: E2B's own
jupyter.service/code-interpreter.service run as root regardless of the
build's own set_user, and E2B's private server hardcodes /home/user,
independent of anything the build sets. Updates TestE2B's docstring and
xfail reason to describe that, instead of the superseded "the build
itself refuses" story -- and the module docstring's "everything is
deleted" claim, which was never quite true for E2B (no template-delete
call exists) and matters more now that a live run actually reaches a real
template every time.

Text-only, like the previous Modal xfail correction: this branch's own
adapters are still the pre-E2-03/E2-05 stubs (cut from main), so nothing
here is runnable proof -- it keeps this file honest about what to expect
once #29 and #31 merge.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
…e-matrix

# Conflicts:
#	code_sandboxes/modal_sandbox.py
#	tests/test_modal_google_colab_sandbox.py
@echarles
echarles merged commit 4584c8a into main Sep 14, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants