test(e2e): bind-mount /tmp on disk and surface failed tests in CI summary#9570
test(e2e): bind-mount /tmp on disk and surface failed tests in CI summary#9570
Conversation
…mary The Docker-wrapped e2e job was failing on Swift install with `No space left on device`: `run_test` mounted the container's /tmp via `--tmpfs /tmp:exec`, which is RAM-backed and capped at ~half of RAM, so multi-GB extractions like swift blew past it. Replace the tmpfs mounts with disk-backed bind mounts under `RUNNER_TEMP`, mirroring the pattern already used in `registry.yml`. Now that e2e tests run in parallel, a single failure was hard to find in the GitHub UI — the per-test ::group:: blocks are replayed in order but the runner emitted no aggregate failure list. Track failed tests during the in-order replay and emit a stderr block, an `::error::` annotation, and a step-summary section listing each failed test by name. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
There was a problem hiding this comment.
Code Review
This pull request improves E2E test reporting by tracking failed tests and displaying a summary, with specific support for GitHub Actions. It also updates the Docker execution logic to use disk-backed host bind-mounts for /tmp and /root, avoiding capacity limits of RAM-backed tmpfs during large tool installations. A review comment recommends using a shell trap to ensure temporary host directories are cleaned up even if the process is interrupted.
| local rc=0 | ||
| "${docker_args[@]}" "$image" bash /mise-src/e2e/run_test "$TEST" || rc=$? | ||
| rm -rf "$host_tmp" "$host_root" | ||
| return "$rc" |
There was a problem hiding this comment.
The temporary host directories created for Docker bind-mounts are currently only cleaned up if the script reaches the end of the run_in_docker function. If the script is interrupted (e.g., by a SIGINT/Ctrl-C), these directories will remain on the host. Using a trap ensures that cleanup occurs regardless of how the script exits, which is especially useful for local development.
local rc=0
trap 'rm -rf "$host_tmp" "$host_root"' EXIT
"${docker_args[@]}" "$image" bash /mise-src/e2e/run_test "$TEST" || rc=$?
return "$rc"
Greptile SummaryThis PR fixes Swift e2e ENOSPC failures by replacing RAM-backed Confidence Score: 4/5Safe to merge; only P2 style/hardening concerns remain around temp-dir cleanup on interrupt and the missing exec mount flag. All findings are P2. The bind-mount approach is correct and mirrors the existing pattern in registry.yml. The failure-reporting logic in run_all_tests is straightforward and handles edge cases well. e2e/run_test — the missing exec mount option and the lack of an interrupt-safe cleanup trap are worth a second look before the script runs on self-hosted runners. Important Files Changed
Sequence DiagramsequenceDiagram
participant RA as run_all_tests
participant RT as run_test (child)
participant D as Docker container
RA->>RT: spawn (parallel, up to E2E_JOBS)
RT->>RT: mktemp host_tmp, host_root
RT->>RT: chmod 777 host_tmp host_root
RT->>D: docker run --rm --read-only -v host_root:/root -v host_tmp:/tmp
D-->>RT: exit (rc)
RT->>RT: rm -rf host_tmp host_root
RT-->>RA: write status file + log
RA->>RA: in-order replay of logs
alt test_status != 0
RA->>RA: FAILED_TESTS += test_name
end
RA->>RA: print Failed e2e tests to stderr
opt GITHUB_ACTIONS set
RA->>RA: echo ::error title=E2E failures::...
opt GITHUB_STEP_SUMMARY set
RA->>RA: append Failed e2e tests section to summary
end
end
Reviews (1): Last reviewed commit: "test(e2e): bind-mount /tmp on disk and s..." | Re-trigger Greptile |
| local rc=0 | ||
| "${docker_args[@]}" "$image" bash /mise-src/e2e/run_test "$TEST" || rc=$? | ||
| rm -rf "$host_tmp" "$host_root" | ||
| return "$rc" |
There was a problem hiding this comment.
Temp dirs not cleaned up on interrupt
rm -rf "$host_tmp" "$host_root" only runs when the Docker command returns normally (or with a non-zero exit). If the outer run_all_tests process receives SIGINT/SIGTERM and kills its child processes via its cleanup trap, the run_in_docker bash function is terminated before it reaches the rm -rf line, leaving both directories on disk. On ephemeral GHA runners this is harmless since RUNNER_TEMP is wiped post-job, but for local/long-lived runners the dirs accumulate silently.
A local trap inside run_in_docker would ensure cleanup:
local rc=0
trap 'rm -rf "$host_tmp" "$host_root"' EXIT
"${docker_args[@]}" "$image" bash /mise-src/e2e/run_test "$TEST" || rc=$?
trap - EXIT
rm -rf "$host_tmp" "$host_root"
return "$rc"| host_tmp="$(mktemp -d "$host_tmp_root/mise-e2e-tmp.XXXXXX")" | ||
| host_root="$(mktemp -d "$host_tmp_root/mise-e2e-root.XXXXXX")" | ||
| chmod 777 "$host_tmp" "$host_root" |
There was a problem hiding this comment.
Bind-mount lacks explicit
exec option
The previous --tmpfs /root:exec --tmpfs /tmp:exec explicitly set the exec mount flag, which is required to run binaries installed under those paths inside the container. The replacement -v bind-mounts inherit the host filesystem's mount flags; if RUNNER_TEMP (or TMPDIR) is on a filesystem mounted with noexec (possible in some self-hosted or hardened runner configurations), any tool installed into /root (the container's home dir) will be non-executable and tests will silently fail.
Consider adding :exec to the Docker mount options or documenting that the host path must be on an exec filesystem:
-v "$host_root:/root:exec"
-v "$host_tmp:/tmp:exec"Note: Docker's -v option does support per-volume mount options in the form src:dst:options.
|
Looked at both review suggestions — neither applies to this codebase:
This comment was generated by an AI coding assistant. |
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.28 x -- echo |
20.6 ± 1.1 | 18.4 | 24.7 | 1.00 |
mise x -- echo |
20.7 ± 1.0 | 18.5 | 24.8 | 1.01 ± 0.07 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.28 env |
19.7 ± 1.0 | 17.4 | 23.9 | 1.00 |
mise env |
20.2 ± 1.1 | 18.0 | 25.1 | 1.03 ± 0.08 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.28 hook-env |
20.5 ± 1.0 | 18.5 | 24.3 | 1.00 |
mise hook-env |
20.8 ± 1.0 | 18.9 | 25.7 | 1.02 ± 0.07 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.4.28 ls |
17.1 ± 1.0 | 15.1 | 22.7 | 1.00 |
mise ls |
17.5 ± 0.9 | 15.8 | 21.3 | 1.03 ± 0.08 |
xtasks/test/perf
| Command | mise-2026.4.28 | mise | Variance |
|---|---|---|---|
| install (cached) | 125ms | 129ms | -3% |
| ls (cached) | 60ms | 62ms | -3% |
| bin-paths (cached) | 65ms | 66ms | -1% |
| task-ls (cached) | 546ms | 546ms | +0% |
### 🚀 Features - **(conda)** graduate conda backend out of experimental by @jdx in [#9544](#9544) - **(deps)** Add dart and flutter providers by @tjarvstrand in [#9505](#9505) - **(registry)** add neo4j by @mnm364 in [#9525](#9525) - **(registry)** add rustfs by @mnm364 in [#9530](#9530) - **(task)** support exclusion patterns in task sources by @jlarmstrongiv in [#9496](#9496) - **(vfox)** add stat function to lua file module by @esteve in [#9497](#9497) ### 🐛 Bug Fixes - **(backend)** flag regex prerelease versions by @jdx in [#9500](#9500) - **(backend)** mark -nightly/-canary/-experimental as prereleases by @jdx in [#9523](#9523) - **(backend)** suppress no-versions warning for unresolved-latest backends by @jdx in [#9548](#9548) - **(backend)** include dotnet prereleases from package flags by @jdx in [#9551](#9551) - **(backend)** scope PEP 440 prerelease detection to Python backends by @jdx in [#9558](#9558) - **(cargo)** Apply install_env during cargo install by @c22 in [#9502](#9502) - **(copr)** drop epel-9 chroots since rust >= 1.91 is unavailable by @jdx in [#9484](#9484) - **(github)** skip attestations on non-default api_url by @jdx in [#9486](#9486) - **(github)** retry ip allow list errors without auth by @risu729 in [#9506](#9506) - **(http)** update versions host tracking endpoint by @jdx in [#9527](#9527) - **(install)** don't warn for configured tools when version is passed via CLI by @jdx in [#9522](#9522) - **(install)** refresh latest before installing missing tools by @jdx in [#9545](#9545) - **(install)** don't cache nonexistent install paths by @jdx in [#9553](#9553) - **(lockfile)** don't propagate ad-hoc CLI overrides into the project lockfile by @jdx in [#9562](#9562) - **(plugin)** detect plugin types after cloning by @risu729 in [#9540](#9540) - **(release)** pass --no-git-checks to aube publish by @jdx in [#9483](#9483) - **(task)** convert PATH to MSYS Unix form when spawning POSIX shells on Windows by @JamBalaya56562 in [#9547](#9547) ### 📚 Documentation - **(contributing)** require popularity check for registry PRs by @jdx in [7bbeebe](7bbeebe) - **(watch)** update pitchfork domain to en.dev by @risu729 in [#9536](#9536) - document ghtkn GitHub token setup by @jdx in [#9546](#9546) - clarify registry backend acceptance policy by @jdx in [#9543](#9543) - Change exec command to use bash for variable echo by @kuboon in [#9567](#9567) ### 🧪 Testing - **(e2e)** run test-tool targets in parallel by @jdx in [#9564](#9564) - **(e2e)** run tests in parallel by @jdx in [#9563](#9563) - **(e2e)** bind-mount /tmp on disk and surface failed tests in CI summary by @jdx in [#9570](#9570) - **(tasks)** migrate test_task_help atask to usage field by @jdx in [#9549](#9549) ### 📦️ Dependency Updates - update fedora:45 docker digest to 8b838b3 by @renovate[bot] in [#9507](#9507) - update ghcr.io/jdx/mise:deb docker digest to f02194c by @renovate[bot] in [#9509](#9509) - update taiki-e/install-action digest to 7769b73 by @renovate[bot] in [#9512](#9512) - update ghcr.io/jdx/mise:alpine docker digest to 581f8a8 by @renovate[bot] in [#9508](#9508) - update rust crate ctor to v0.10.1 by @renovate[bot] in [#9515](#9515) - update ghcr.io/jdx/mise:rpm docker digest to a5c9655 by @renovate[bot] in [#9510](#9510) - update rust docker digest to a9cfb75 by @renovate[bot] in [#9511](#9511) - update rust crate age to v0.11.3 by @renovate[bot] in [#9514](#9514) - update rust crate jiff to v0.2.24 by @renovate[bot] in [#9516](#9516) - update dependency vitepress-plugin-tabs to ^0.9.0 by @renovate[bot] in [#9518](#9518) - update autofix-ci/action action to v1.3.4 by @renovate[bot] in [#9513](#9513) - update rust crate usage-lib to v3.2.1 by @renovate[bot] in [#9517](#9517) - update apple-actions/import-codesign-certs action to v7 by @renovate[bot] in [#9519](#9519) - update taiki-e/install-action digest to 51cd0b8 by @renovate[bot] in [#9531](#9531) - exclude taiki-e/install-action from renovate by @jdx in [#9532](#9532) - update rust crate blake3 to v1.8.5 by @renovate[bot] in [#9533](#9533) ### 📦 Registry - enable shellcheck on windows by @zeitlinger in [#9487](#9487) - add google-java-format by @zeitlinger in [#9488](#9488) - add expert ([aqua:expert-lsp/expert](https://github.com/expert-lsp/expert)) by @AlternateRT in [#9498](#9498) - update entry for checkmake by @eread in [#9504](#9504) - add systemctl-tui ([aqua:rgwood/systemctl-tui](https://github.com/rgwood/systemctl-tui)) by @2xdevv in [#9521](#9521) - add codon by @3w36zj6 in [#9538](#9538) - add tool yr (backend:github:VirusTotal/yara-x) by @adam-moss in [#9542](#9542) - add tool betterleaks (backend:aqua/betterleaks/betterleaks) by @adam-moss in [#9541](#9541) - add `git-filter-repo` by @garysassano in [#9550](#9550) - add umoci ([aqua:opencontainers/umoci](https://github.com/opencontainers/umoci)) by @2xdevv in [#9555](#9555) - add aqua backend for elixir-ls by @AlternateRT in [#9557](#9557) - deny inline backend options by @risu729 in [#9565](#9565) ### Chore - **(ci)** fail registry tests without summary by @jdx in [#9559](#9559) - **(ci)** use !cancelled() instead of always() for test-ci aggregator by @jdx in [#9569](#9569) - **(ci)** use namespace runners for ci jobs by @jdx in [#9561](#9561) - **(config)** deprecate shorthands_file setting by @risu729 in [#9534](#9534) - **(docs)** remove shrill.en.dev analytics script by @jdx in [#9539](#9539) - **(release)** replace bc with awk in release-plz star formatting by @jdx in [d7f177f](d7f177f) - bump hk to 1.44.3 by @jdx in [#9493](#9493) - invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by @jdx in [#9560](#9560) - set dev profile debug to 1 by @jdx in [#9572](#9572) ### New Contributors - @kuboon made their first contribution in [#9567](#9567) - @AlternateRT made their first contribution in [#9557](#9557) - @2xdevv made their first contribution in [#9555](#9555) - @adam-moss made their first contribution in [#9541](#9541) - @jlarmstrongiv made their first contribution in [#9496](#9496) - @tjarvstrand made their first contribution in [#9505](#9505)
## Summary - wait for successful e2e test environment cleanup when the test is running inside Docker - keep asynchronous cleanup outside Docker so local/non-Docker runs keep the existing fast path - remove the need for a second cleanup container that chmods the Docker bind mounts ## Context #9570 changed Docker e2e runs to bind-mount host-backed directories for `/tmp` and `/root` instead of using tmpfs. Inside the container, `mktemp -d` creates root-owned `0700` per-test directories under the bind-mounted `/tmp`. For successful tests, `e2e/run_test` previously started `rm -rf "$TEST_ISOLATED_DIR"` in the background. When the Docker container exits before that background cleanup reliably finishes, root-owned `0700` test directories can be left behind on the host mount. The outer host-side cleanup then emits permission noise such as `rm: cannot remove`. Waiting for `rm -rf` only when `MISE_E2E_INSIDE_DOCKER=1` fixes the source of that host-side cleanup noise without running a second container or broadly chmodding both mounts. Failed tests keep the existing behavior: the isolated test environment is left in place and reported for inspection, and the returned test status remains unchanged. ## Tests - `git diff --check` - `bash -n e2e/run_test` - `mise x shellcheck -- shellcheck -x e2e/run_test` - `mise x shfmt -- shfmt -d e2e/run_test` - `MISE_E2E_DOCKER=1 E2E_WAIT_FOR_GH_RATE_LIMIT=0 mise run test:e2e e2e/cli/test_version` - verified captured output had no `rm: cannot remove` lines
Summary
e2e/run_test's Docker invocation mounted the container's/tmpand/rootvia--tmpfs, which is RAM-backed and capped at ~half of RAM. Multi-GB tool extractions (e.g. swift) blew past that limit (No space left on device (os error 28)). Replaced with disk-backed bind mounts underRUNNER_TEMP, mirroring the pattern already used in.github/workflows/registry.yml.::group::blocks are still replayed in order but the runner emitted no aggregate failure list, making it tedious to find which test failed in the GitHub Actions log.e2e/run_all_testsnow tracks failed tests during the in-order replay and emits:::error title=E2E failures (N)::test1,test2,...annotation that surfaces in the workflow's annotations panel### :x: Failed e2e tests (N)section in the GHA step summaryTest plan
bash -nandshellcheck -xclean one2e/run_testande2e/run_all_testsmise run lint-fixclean::error::line, and step-summary content shape)🤖 Generated with Claude Code
Note
Medium Risk
Medium risk because it changes how e2e tests run in Docker (/tmp and /root mounts) and alters CI reporting; failures could show up as new CI flakiness or permission/cleanup issues rather than product behavior.
Overview
Improves e2e CI ergonomics and reliability.
e2e/run_testreplaces Docker--tmpfsusage for/tmpand/rootwith disk-backed bind mounts (created under${MISE_E2E_HOST_TMP:-${RUNNER_TEMP:-${TMPDIR:-/tmp}}}) to avoid ENOSPC on large tool installs, and ensures those temp dirs are cleaned up while preserving the container exit code.e2e/run_all_testsnow tracks which parallel tests failed and, at the end of the run, emits an aggregated failure list to stderr and (on GitHub Actions) a::errorannotation plus a “Failed e2e tests” section appended to the step summary.Reviewed by Cursor Bugbot for commit c5ce6ec. Bugbot is set up for automated code reviews on this repo. Configure here.