test: prove the in-sandbox min add path via interactive PTY attach on every lane - #760
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 39 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe change adds process termination status to git command errors and updates their formatting. It also expands session E2E coverage with PTY-driven attach teardown, temporary project seeding, non-baseline tool checks, cleanup assertions, and gvproxy provisioning in Linux KVM and macOS workflows. ChangesGit status reporting
Session E2E validation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CIWorkflow
participant SessionE2E
participant PTYHelper
participant MinAttach
CIWorkflow->>SessionE2E: Run session E2E with MINVMD_GVPROXY_BIN
SessionE2E->>PTYHelper: Start interactive attach proof
PTYHelper->>MinAttach: Execute attach through PTY
MinAttach-->>PTYHelper: Return tool output and exit prompt
PTYHelper->>MinAttach: Select Delete
PTYHelper-->>SessionE2E: Return captured output and status
SessionE2E-->>CIWorkflow: Report lifecycle assertions
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
9735bae to
6cc1805
Compare
6cc1805 to
1761725
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/minimald/src/session_host.rs`:
- Around line 467-489: Wrap the async_dialog::Select interaction in the
interactive exit flow with the existing detach-timeout mechanism, treating
timeout expiry as a detach while preserving the current selection, cancellation,
and error handling behavior. Update the match around interact in the session
exit logic so an expired prompt follows the same path as Ok(Selection::At(0)) or
Ok(Selection::Cancelled).
In `@scripts/session-e2e.sh`:
- Around line 226-240: Update the command stream assembled in the exec_out block
so the TOOL_ABSENT_BEFORE marker is constructed dynamically at execution time
rather than appearing literally in the echoed input. Keep the existing grep
assertion unchanged, ensuring only actual command output can satisfy the absence
check.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 599010d2-1986-49dc-95dc-7320626ba61c
📒 Files selected for processing (8)
crates/checkouts/src/error.rscrates/checkouts/src/repo.rscrates/mctx/src/error.rscrates/minimal/src/lib.rscrates/minimald/src/lib.rscrates/minimald/src/session.rscrates/minimald/src/session_host.rsscripts/session-e2e.sh
a743cc7 to
d125083
Compare
| "UserKnownHostsFile=/dev/null", | ||
| ]); | ||
| ssh.env("MINIMAL_SESSION_ID", record.id.to_string()) | ||
| .env("MINIMAL_INTERACTIVE", if interactive { "1" } else { "0" }) |
There was a problem hiding this comment.
The canonical way to signal interactivity is whether the ssh process requests a pty or not, if i recall correctly its -tvs -T.
| // Whether the attaching client's own terminal is interactive; drives the | ||
| // session-exit prompt (interactive) vs. delete-on-exit (non-interactive) | ||
| // decision in the binding. | ||
| let interactive = config.interactive(); |
There was a problem hiding this comment.
The more I think about this, I don't think we should do this. The intent of a session is always interactive, its things like tasks and such that are intended for the more deterministic / exec-like execution. Lets use expect and/or pump keystrokes?
There was a problem hiding this comment.
Honestly, I could see this both ways.
While I agree that our intent of a session as spec'ed was interactive and non-deterministic, generally I try to avoid adding unnecessary constraints.
Additionally, there could be a use case here. non-interactive and non-determinsitic are not mutually exclusive. I could see passing a session to an agent running in prompt mode without a PTY.
I'm going to leave this one in draft mode while I figure out some deeper issues around why the DNS isn't resolving inside a github runner.
| # baseline (base/coreutils/socat), then run it: the tool being absent | ||
| # before and runnable after proves `min add` reached the daemon over the | ||
| # in-sandbox `/run/minenv_sock` relay and hardlinked the package into the | ||
| # live rootfs. `min add` with no flag defaults to `--session` (this session |
There was a problem hiding this comment.
Did we implement --session ?
There was a problem hiding this comment.
I don't think so. I have no idea where this came from. My clanker is getting testy with this one.
| # `--command`) opens a PTY-backed shell; we pipe it a command stream ending | ||
| # in `exit`. When the shell exits, the daemon's session-exit prompt would | ||
| # historically block waiting for an arrow-key answer. The `min` client now | ||
| # reports whether its own context is a tty (MINIMAL_INTERACTIVE); a |
There was a problem hiding this comment.
Lets just use expect or pump keystrokes instead of this MINIMAL_INTERACTIVE thing.
92a237c to
27d3394
Compare
A failed git command surfaced only its stderr. When git is killed by a signal (e.g. OOM mid-fetch) stderr holds only whatever it wrote before dying — which can be a misleadingly benign line such as a "templates not found" warning, masking that the process was killed rather than failing cleanly. Include the rendered ExitStatus (exit code or signal) in GitCommandFailed and its mctx wrapper, so a signal kill is distinguishable from a git-reported error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50e1f24 to
c2d4a74
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/ci-macos.yml (1)
331-359: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winMove gvproxy provisioning out of the frozen workflow layer.
Make the shared session E2E script provision and export gvproxy for VM lanes, then revert these workflow edits.
As per coding guidelines, “Do not edit files under
.github/workflows/; the CI layer is frozen and CODEOWNER-gated,” and extend coverage throughscripts/orjustfile. Based on learnings, this test expansion belongs in convention-discovered scripts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci-macos.yml around lines 331 - 359, Move gvproxy provisioning from the macOS workflow step into the shared session E2E implementation, updating the relevant script or justfile target to fetch and export the VM-lane gvproxy path before minvmd runs. Remove the workflow-specific provisioning and related setup changes, while preserving the existing session E2E behavior and using the established MINVMD_GVPROXY_BIN configuration.Sources: Coding guidelines, Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci-linux-kvm.yml:
- Around line 338-357: Remove the gvproxy provisioning step and related
MINVMD_GVPROXY_BIN workflow configuration from the KVM lane in the workflow.
Update scripts/session-e2e.sh to provision a temporary gvproxy when
MINVMD_GVPROXY_BIN is unset, while preserving externally supplied binaries and
the existing session-e2e flow.
In `@scripts/session-e2e.sh`:
- Around line 91-100: Update the VM seed setup in the E2E project-directory
branch to create a unique temporary directory using mktemp instead of the fixed
/tmp/mnl-e2e-project path. Preserve assigning the generated directory to
PROJECT_DIR and remove only that run’s directory during setup, preventing
concurrent VM runs from deleting each other’s seed data.
---
Outside diff comments:
In @.github/workflows/ci-macos.yml:
- Around line 331-359: Move gvproxy provisioning from the macOS workflow step
into the shared session E2E implementation, updating the relevant script or
justfile target to fetch and export the VM-lane gvproxy path before minvmd runs.
Remove the workflow-specific provisioning and related setup changes, while
preserving the existing session E2E behavior and using the established
MINVMD_GVPROXY_BIN configuration.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cda574d2-4331-48d9-82e4-354bb57e6780
📒 Files selected for processing (7)
.github/workflows/ci-linux-kvm.yml.github/workflows/ci-macos.ymlcrates/checkouts/src/error.rscrates/checkouts/src/repo.rscrates/mctx/src/error.rsscripts/e2e-attach-pty.pyscripts/session-e2e.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/checkouts/src/error.rs
- crates/mctx/src/error.rs
The shared session e2e only proved the lifecycle; it never forked a session sandbox. Deepen it, on every lane, to attach an interactive shell — which forks a real hakoniwa sandbox (on a VM lane, inside the guest over the vsock bridge) — and prove the in-sandbox `min` helper: record a genuinely-absent tool (jq — the shell stack composes base + curl, and jq is pulled in by neither) as absent, `min add` it, then run it; its version banner round-tripping proves `min add` reached the daemon over the /run/minenv_sock relay and hardlinked the package into the live rootfs. A session is interactive by design, so the proof drives it like a real user through a REAL pty (scripts/e2e-attach-pty.py): pump the command stream, then answer the session-exit Detach/Delete prompt with keystrokes (Down + Enter => Delete). A pipe is not a tty and cannot answer that prompt. Selecting Delete tears the session down, so it must be delisted. Output is matched with a bash glob, not `printf | grep -q`, so `min add`'s progress-bar flood cannot SIGPIPE a pipefail false-negative. A host-side minimal.toml is seeded (uploaded to the session since #748) so the client pre-flight neither prompts nor bails (#758); every seed is removed on teardown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The KVM and macOS session-e2e lanes boot a microVM whose guest daemon clones the upstream `pkgs` repo during session mint. That clone needs the guest's egress — NAT + DNS — which minvmd provides by spawning gvproxy as the per-host-VM switch. Neither lane provisioned the gvproxy binary, and its resolution is best-effort (never errors), so minvmd booted the VM switchless: the guest's overlay had no other end and every egress — including the clone — died with "Could not resolve host: github.com". Fetch the pinned gvproxy (scripts/fetch-gvproxy.sh, already used by the native minimald-root-integration job) and point minvmd at it via MINVMD_GVPROXY_BIN on each lane's session-e2e step. The macOS lane's fetch auto-selects the gvproxy-darwin asset. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c2d4a74 to
0b73aaa
Compare
…ox proof The native-e2e lane dropped its userns sysctl step on the rationale that session-e2e.sh forks no sandbox — true when written, falsified by the in-sandbox `min add` proof (#760) merged since: on a restricted runner the proof's sandbox dies at uid_map and surfaces as the misleading 'jq was already present' error (the absent-marker never prints). Load the shipped profile from the script itself, attached to the minimald the run will spawn — dogfooding the documented remediation on CI and on any restricted host running the e2e locally. Native Linux only: VM lanes create their userns in-guest as root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ox proof The native-e2e lane dropped its userns sysctl step on the rationale that session-e2e.sh forks no sandbox — true when written, falsified by the in-sandbox `min add` proof (#760) merged since: on a restricted runner the proof's sandbox dies at uid_map and surfaces as the misleading 'jq was already present' error (the absent-marker never prints). Load the shipped profile from the script itself, attached to the minimald the run will spawn — dogfooding the documented remediation on CI and on any restricted host running the e2e locally. Native Linux only: VM lanes create their userns in-guest as root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(minimald): ship an AppArmor profile granting minimald userns
Ubuntu 24.04+ defaults kernel.apparmor_restrict_unprivileged_userns=1,
which bars unconfined programs from creating unprivileged user
namespaces. Every minimald session is one (hakoniwa forks and the child
writes /proc/self/uid_map), so on a stock 24.04 host no session can
start: the child dies with EPERM before it runs, surfacing only as an
opaque uid_map DIAG in the daemon log.
Ship the accommodation Ubuntu intends, the same shape the distro ships
for rootlesskit, runc and podman: an unconfined-mode profile whose sole
purpose is to give minimald a named label carrying the `userns`
permission. It confines nothing, and unlike setting the sysctl to 0 it
does not hand user namespaces back to every other program on the host.
minimald has no single install path (the installer puts it in
${MINIMAL_BIN:-~/.local/bin}, a package would use /usr/bin), and AppArmor
attaches profiles by executable path, so the attachment set lives in a
tunable; --path extends it for dev builds via tunables/minimald.d.
Verified on Ubuntu with the restriction on: without the profile an
interactive attach yields no sandbox shell; with it loaded the daemon
runs as `minimald (unconfined)` and the shell comes up in its own user
namespace. Unconfined processes remain unable to unshare.
Refs: #610
* fix(scripts): assign SRC_DIR before readonly in the apparmor installer
shellcheck SC2155: `readonly VAR=$(cmd)` masks the command's exit
status, so a missing packaging/apparmor dir would go unnoticed instead
of failing the script.
* ci: drop dead userns sysctl from the native e2e job
native-daemon-e2e lifted Ubuntu's unprivileged-userns restriction
(`sysctl ...=0`) on the theory that the session sandbox needs it. It does
not: session-e2e.sh drives activate/list/destroy/stop only and never
forks a session sandbox, so the job needs no user namespace — verified by
running the e2e green with the restriction left on. Remove the step and
leave a comment so it is not re-added; correct the job header, which
wrongly claimed the e2e execs inside the sandbox (it does not — that path
is proven in Rust, per session-e2e.sh).
Sandbox-forking coverage that genuinely needs userns stays put: the
workspace tests and the minimald-root-integration job (whose own sysctl
step is unaffected — its userns comes from the cargo test binary, not
minimald).
Refs: #610
* feat(installer): ship and advise the minimald AppArmor profile
The curl|sh installer had no path to the AppArmor profile that lets
minimald create the unprivileged user namespace every session sandbox
needs: the profile and its loader lived only in the source tree, so
`sudo scripts/install-apparmor-profile.sh` was a dead reference for
anyone who installed via curl|sh. On stock Ubuntu 24.04+ that left every
session dying at uid_map with an opaque EPERM and no in-product hint.
Plumb it in without ever elevating from the installer:
- stage-release.sh ships packaging/apparmor/{minimald,tunables/minimald}
and install-apparmor-profile.sh as data-prefix components, installed to
~/.local/share/minimal/apparmor/.
- install.sh prints a gated, Linux-only advisory when it detects
apparmor_restrict_unprivileged_userns=1, pointing at the shipped loader
(sudo bash ...). Advice only; it never runs sudo.
- On --uninstall, offer to remove the system profile too: prompt on a
terminal, advise the root command otherwise, run before the record walk
deletes the loader. Never touches /etc/apparmor.d unattended.
- install-apparmor-profile.sh resolves its source dir from either the
checkout layout or the shipped-flat layout.
- The install loop now creates each component's parent dir, so a
component whose subpath nests directories installs correctly.
install_test.sh covers placement, the advisory on/off, darwin-skip, and
the uninstall advisory (no elevation, loader removed).
Refs: #610
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(installer): skip the userns advisory when the profile is installed
The install-time advisory checked only the sysctl and the shipped
loader, so a reinstall on an already-remediated host still claimed the
sandbox "cannot start" — the profile is loaded and sessions work, yet
the sysctl stays 1. Gate the advisory on the absence of the installed
profile at /etc/apparmor.d/minimald (via the same MINIMAL_OVERRIDE_-
APPARMOR_DIR seam the uninstall path uses), so it fires only on a host
that is restricted and not yet remediated.
Also show the loader's output in the curl-installed docs example, so the
command block carries output like its sibling (satisfies markdownlint
MD014).
Both raised by CodeRabbit on #740.
* feat(sandbox2,minimald): preflight the userns restriction at daemon startup
Every session sandbox starts by unsharing an unprivileged user
namespace, forked from the daemon with no exec in between. On a host
that refuses it (stock Ubuntu 24.04+ with an unconfined daemon, or user
namespaces disabled outright) the denial previously surfaced only when
the first attach died writing /proc/self/uid_map, with nothing useful
in the daemon log.
sandbox2 gains user_namespaces_restriction() — a read-only probe of the
userns quota, the apparmor_restrict_unprivileged_userns sysctl, euid,
and the process's own AppArmor label, mirroring the crate's existing
net-namespace probe — and minimald warns at startup naming the
restriction and the exact fix. Root (the in-guest microVM pid-1) and
confined daemons stay silent, so the common case logs nothing.
Refs: #610
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(installer): point the userns advisory at docs.minimal.dev
The reference docs ship to https://docs.minimal.dev/ with this release,
so the advisory's details link should point there rather than at a
GitHub blob URL. The build-specs link to minimal.ncl stays on GitHub:
it targets source, not a hosted doc page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(minimald,paths): derive the advisory's loader path and attach the running binary
The startup warning hardcoded ~/.local/share for the shipped loader, so
the advised command was wrong whenever XDG_DATA_HOME was set, and it
omitted --path, so a daemon outside the tunable's stock attachment set
(a custom MINIMAL_BIN, a dev build) was told to load a profile that
would never attach to it. paths gains minimal_data_dir(), resolving
exactly like the installer's data prefix, and the fix string now carries
--path <current_exe>.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scripts): preserve arguments in the loader's sudo hint
The parse loop consumes $@ before the root check, so the hinted
`sudo $0 $*` always printed bare — a copy-pasted retry of
--uninstall would install, and one of --path would skip the attachment.
Capture the original argv up front and print that.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(installer): cover custom MINIMAL_BIN in advisory; stop the daemon only for bin/lib swaps
Two review findings against the advisory and the R5.5 stop:
- The stock tunable attaches only /usr/bin, /usr/local/bin, and
~/.local/bin, so for a custom MINIMAL_BIN the advised command now
carries --path "$bindir/minimald", and a loaded profile counts as
remediation only when the tunables actually name that binary —
previously the advisory suppressed itself while sessions stayed
broken.
- stop_running_daemon now fires only for bin/lib component swaps:
replacing a data file (the shipped apparmor text) must not kill live
sessions to swap a file the running daemon does not serve from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(release): stage repo files into the workdir so --dry-run touches nothing
stage_repo_file wrote the three apparmor files into ARTIFACTS_DIR before
the dry-run branch, mutating a directory the caller owns (and dying
outright when it is read-only). Stage into the ephemeral workdir and let
the manifest loop prefer staged files over same-named artifacts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scripts): load the minimald AppArmor profile before the e2e sandbox proof
The native-e2e lane dropped its userns sysctl step on the rationale that
session-e2e.sh forks no sandbox — true when written, falsified by the
in-sandbox `min add` proof (#760) merged since: on a restricted runner
the proof's sandbox dies at uid_map and surfaces as the misleading
'jq was already present' error (the absent-marker never prints).
Load the shipped profile from the script itself, attached to the
minimald the run will spawn — dogfooding the documented remediation on
CI and on any restricted host running the e2e locally. Native Linux
only: VM lanes create their userns in-guest as root.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#760 deepened session-e2e.sh with the in-sandbox min add proof, whose session mint clones the pkgs upstream inside the guest, and added fetch-gvproxy + MINVMD_GVPROXY_BIN to the KVM and macOS lanes — but not to nightly-tests' session-e2e-soak. With no gvproxy staged, minvmd boots the VM switchless (resolution is best-effort and never errors), the guest has no egress or DNS, and every one of the soak's 10 iterations dies with 'Could not resolve host: github.com' (first seen on the Jul 16 scheduled run, the first after #760 merged). Stage the pinned gvproxy and point MINVMD_GVPROXY_BIN at it, exactly as the KVM lane does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…efore pushing unstable (#759) * ci(release): smoke-test the shipped artifacts before publish Prove the just-built RELEASE binaries actually run before publishing any of them. Three new jobs download the uploaded artifacts by name, put the SHIPPED binaries on PATH (never a fresh target/ build), and run the ONE shared session e2e (scripts/session-e2e.sh — the same proof the CI lanes run): - smoke-linux-amd64 (ubuntu): DM2 native minimald — the static-musl min/minimald boot a host daemon and pass the session e2e. - smoke-linux-kvm (KVM-capable ubuntu): DM3 — boot a real microVM from the shipped native-glibc minvmd + shipped guest kernel/rootfs/initramfs + pinned gvproxy; libkrun is materialized from the upstream package (not shipped on Linux) via the shipped static mip. - smoke-macos (self-hosted mini): DM1 — lay the SIGNED min/minvmd/libkrun.1.dylib/ gvproxy + arm64 guest payload into the installer layout (libkrun.1.dylib a lib/ sibling of bin/, resolved via @loader_path/../lib) and boot the microVM. A new skip-tolerant smoke-success aggregator (if: always(), fails only on failure/cancellation) is added to the release job's needs, so a smoke failure blocks every publish step while a skipped smoke-macos (RUN_MACOS_CI kill-switch) reads as pass. Smoke has no !inputs.dry_run guard: a dry run builds, signs, assembles, and SMOKES, then publishes nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): grant nightly the checks scope its release call needs release.yml's verify-ci gate (#757) requests checks: read at the job level. When release.yml runs as a workflow_call from nightly.yml, it cannot hold any scope the caller's permissions block doesn't grant, and nightly.yml granted only contents: write and id-token: write — so every scheduled run since #757 merged died at workflow compile time with startup_failure (Jul 15 and Jul 16), cutting no nightly release. Manual release.yml dispatches kept working because a directly-dispatched workflow uses its own permissions, which is why this went unnoticed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(nightly): smoke the shipped artifacts before advancing the channel Move the three smoke jobs and their skip-tolerant aggregator from release.yml into nightly.yml. The workflow_call jobs are part of the caller's run, so each smoke job still downloads the just-built artifacts by name and runs the one shared session e2e against the shipped bytes; what changes is what the smoke gates. In release.yml it gated the publish step of every release; here it gates promote-nightly, so a failed smoke leaves the nightly channel pointing at yesterday's build instead of blocking the release machinery itself. On a no-op night (HEAD already staged) release and the smokes both skip and the channel flip proceeds against bytes that were smoked the night they were cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): point the smoke VMs at the shipped gvproxy explicitly minvmd never resolves gvproxy from PATH — resolve_gvproxy_path() honours only MINVMD_GVPROXY_BIN, the installer bin dir ($MINIMAL_BIN / ~/.local/bin), and /usr/lib/minimal/bin. Renaming the shipped binary onto PATH therefore left both VM smokes switchless: gvproxy resolution is best-effort, so minvmd boots without guest egress and the in-guest pkgs clone during session mint dies with 'Could not resolve host' — the exact 10/10 failure the Jul 16 session-e2e-soak hit for the same reason. Set MINVMD_GVPROXY_BIN on both session-e2e steps, as the KVM and macOS lanes do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): stage gvproxy for the session-e2e soak #760 deepened session-e2e.sh with the in-sandbox min add proof, whose session mint clones the pkgs upstream inside the guest, and added fetch-gvproxy + MINVMD_GVPROXY_BIN to the KVM and macOS lanes — but not to nightly-tests' session-e2e-soak. With no gvproxy staged, minvmd boots the VM switchless (resolution is best-effort and never errors), the guest has no egress or DNS, and every one of the soak's 10 iterations dies with 'Could not resolve host: github.com' (first seen on the Jul 16 scheduled run, the first after #760 merged). Stage the pinned gvproxy and point MINVMD_GVPROXY_BIN at it, exactly as the KVM lane does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Deepens the shared session e2e (
scripts/session-e2e.sh) so that, on everydeployment lane (native DM2, KVM DM3, macOS DM1), it forks a real session
sandbox and proves the in-sandbox
minhelper end to end — not just the sessionlifecycle. Also fixes the CI gap that prevented the VM lanes from running an
in-guest session at all, and improves git-command error reporting.
Three commits.
test: exercise the session sandbox via interactive attachAttaches an interactive shell to a live session (which forks a real hakoniwa
sandbox — on a VM lane, inside the guest over the vsock bridge) and proves
min add: a genuinely-absent tool (jq— theshellstack composesbase+curl, andjqis pulled in by neither) is recorded absent,min added, thenrun; its version banner round-tripping proves
min addreached the daemon overthe
/run/minenv_sockrelay and hardlinked the package into the live rootfs.A session is interactive by design, so the proof drives it like a real user
through a real PTY (
scripts/e2e-attach-pty.py), not a pipe: it pumps thecommand stream, then answers the session-exit Detach/Delete prompt with
keystrokes (Down + Enter → Delete). A pipe is not a tty and cannot answer that
prompt. Selecting Delete tears the session down, so it must then be delisted —
doubling as the interactive-teardown proof. Output is matched with a bash glob,
not
printf | grep -q:min add's progress-bar flood can makegrep -qshort-circuit and SIGPIPE the feeding
printf, which underpipefailbecomes afalse negative.
Host-side seed: since #748
min activateuploads the project into the session, so the e2e seeds a small,pinned
minimal.toml(the repo's[upstream]verbatim, incl.locked_commit, plus a lightshellstack) in a dir it owns and cleans(
rm -rffirst — a persistent/self-hosted runner can carry a stale unpinnedleftover that the graph loader rejects). This also satisfies the
#758 non-interactive
min activatepre-flight. Every seed is removed on teardown.ci: provision gvproxy for the KVM/macOS session-e2e lanesThe VM lanes boot a microVM whose guest daemon clones the upstream
pkgsrepoduring session mint — which needs the guest's egress (NAT + DNS), provided by
the gvproxy switch
minvmdspawns. Neither VM lane provisioned the gvproxybinary, and its resolution is best-effort (never errors), so
minvmdbooted theVM switchless: the guest overlay had no other end and every egress died with
Could not resolve host: github.com. It looks like DNS but is dead L3 egress —raw TCP to literal public IPs returns
EHOSTUNREACH, and even the gateway issilent.
Fetches the pinned gvproxy (
scripts/fetch-gvproxy.sh+vendor/gvproxy/gvproxy.lock, already used by the nativeminimald-root-integrationjob) and pointsminvmdat it viaMINVMD_GVPROXY_BINon each VM lane's session-e2e step. The macOS laneauto-selects the
gvproxy-darwinasset. Native lanes never needed it (minimaldruns on the host with real internet; no overlay).
fix(checkouts): report exit status on git command failureA failed git command surfaced only its stderr, which for a signal-killed process
can be a misleadingly benign line (e.g. a "templates not found" warning that
masks that the process was killed). Includes the rendered
ExitStatus(exit codeor signal) in
GitCommandFailedand itsmctxwrapper.Validated
All three e2e lanes green — native, KVM, and macOS: the in-sandbox
min add jq+run + interactive Delete-teardown round-trips on each. Rebased onto
main(post-#748);
cargo/clippyclean via cross;
shellcheck/bash -n/py_compile/actionlintclean.🤖 Generated with Claude Code
Summary by CodeRabbit