test(scripts): canary the bulk host-to-guest upload path - #887
Conversation
📝 WalkthroughWalkthroughThe change adds a configurable bulk-upload end-to-end proof, generates and validates its upload fixture, runs repeated timed activations with cleanup, exposes the proof through ChangesBulk upload proof
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant BulkUpload as bulk-upload-e2e.sh
participant MinCLI as min CLI
participant Daemon
participant VMGuest as VM guest
BulkUpload->>MinCLI: warm min ls
MinCLI->>Daemon: start or query daemon
Daemon->>VMGuest: initialize guest when applicable
BulkUpload->>MinCLI: timed min activate .
MinCLI->>Daemon: upload project and activate
Daemon->>VMGuest: process activation when applicable
MinCLI-->>BulkUpload: session UUID or timeout
BulkUpload->>MinCLI: destroy session or sweep resources
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Nothing in CI moved enough data through the guest vsock to catch a transport regression: the PR-lane session e2e activates a small project, and a guest-kernel bump broke every sizeable upload for three weeks before a human hit it by hand. Add scripts/bulk-upload-e2e.sh, which activates a project carrying a synthesized ~49 MiB fixture five times and fails if any attempt errors or hangs. The fixture is deliberately compressible — an incompressible payload throttles the writer through the zstd encoder and hides the failure — and its compressed size is asserted at run time so a fixture that stops tripping the bug fails loudly instead of silently disarming the canary. RUST_LOG and MINVMD_KRUN_LOG are pinned, both having been measured to make the failure vanish. Hook it into scripts/soak-session-e2e.sh, which the nightly job already runs; no workflow change is needed. Refs: #869 Co-Authored-By: macroscopeapp[bot] <macroscopeapp[bot]@users.noreply.github.com>
a827cb4 to
7b90e63
Compare
…hildren Both child harnesses cd into their own project dir before invoking `min` (bulk-upload-e2e.sh does it at top level, session-e2e.sh inside the activate subshell), and minvmd consumes MINVMD_BOOT_LOG verbatim — it maps the env value straight to a PathBuf and only warns if the file cannot be created. A relative boot-log dir therefore resolved against the child's project dir and the guest console was discarded silently, losing exactly the diagnostics a failing run needs. Both current callers pass an absolute path, so this is latent, but the directory is a documented positional argument. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@macroscope review |
|
Manual reviews triggered for commit All prior checks · these links stay valid even if you push more commits. |
|
Just FYI for future @mentions, I'm Review triggered. The Approvability and Correctness checks are now running and will post results when complete. |
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. Test-only changes owned by the author, but there's an unresolved high-severity comment about mktemp failure handling that could potentially destroy unrelated developer sessions. This should be addressed before approval. You can customize Macroscope's approvability policy. Learn more. |
`mktemp -d` was unchecked on both the /tmp and $HOME fallbacks, so a failure left WORK empty and the script continued with an empty path. Co-Authored-By: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
8832709 to
66a09de
Compare
What this covers
Failure class: bulk host→guest transfer over the vsock/SSH transport.
A guest kernel bump (6.12.43 → 6.12.94, gominimal/pkgs#311, 2026-07-01) crossed Linux 6.12.92, where a new queued-
sk_buff-overhead check turned a previously silent vsock protocol violation into a fatal connection reset — gominimal/minimal#869. From that day everymin activatecarrying a real project failed:with, guest side in
boot.log:Nothing went red for three weeks, until a human hit it by hand. Nothing we run moves enough data through the guest vsock to trip it —
session-e2e.shuploads a two-lineminimal.toml.scripts/bulk-upload-e2e.shis that missing proof:min activatea project carrying a large, compressible fixture, five times over, failing if any attempt errors or hangs.Why the fixture has to be compressible
The trigger is write rate, not volume, duration or file count — the client has to hand the channel a large number of already-compressed bytes as fast as it can produce them. From the measurements on the issue (three runs each):
So the obvious fixture —
/dev/urandom— is exactly the wrong one: it hides the bug. And 0.11 MB on the wire is equally useless. The fixture therefore has to be large after compression and cheap to compress.Rather than commit a 49 MiB binary, the script synthesizes one: each MiB is a fresh 256 KiB of xorshift32 output written four times, so zstd (level 3, 2 MiB window — what
async_compression'sZstdEncoderdefaults to) resolves three quarters of every MiB to matches while the literal quarter stays incompressible enough to be stored raw. Fixed seed, so it is byte-identical every run and a failure is reproducible.Measured locally: 51,380,224 bytes in → 12,884,004 bytes out (3.99:1) — within 1% of the 12,784,144 bytes that reproduce on the issue — generated in ~0.35 s of perl.
That ratio is then asserted at run time (
zstd -3 -T1, band 8–20 MB): if a future change makes the fixture incompressible, or trivially compressible, the script fails loudly instead of silently disarming itself. The check is skipped with a warning wherezstdis absent (it is on the nightly'subuntu-latest; the justfile already lists it as a macOS prereq).Two related traps are closed the same way: the issue measured 4/4 passes under
MINVMD_KRUN_LOG=debug, and client-sideRUST_LOG=debugis enough on its own to make the bug vanish. The script therefore pinsRUST_LOG=warnand unsetsMINVMD_KRUN_LOG(printing a note when it drops an inherited value) — an inherited debug level would otherwise leave the canary unable to fail.Iteration count: 5
The failure is probabilistic (~89%, not 100%), so one activate is not a detector.
At the measured p = 0.89, three would already be enough (99.87%). Five is chosen for margin against CI hardware that fails to reproduce as reliably: even if the trigger rate halved to p = 0.5, five iterations still detect 96.9% of the time. The cost is five
min activates against one already-booted VM (the daemon is warmed outside the loop, so a VM boot is not billed to any iteration's deadline).Any single failure across the set fails the script (
$fail -eq 0 && $pass -eq $ITER— the same shapesoak-session-e2e.shuses, which also refuses to call a short-circuited loop a pass).Timeout handling (the hang case)
gominimal/inbox#335: the same defect sometimes hangs the client indefinitely instead of erroring (observed at 9 and 13 minutes before being killed). A canary that waits forever is a canary that reports nothing, so:
min activateruns under a wall-clock deadline (default 420 s,BULK_ACTIVATE_TIMEOUT_SECS), SIGTERM then SIGKILL, returning 124;the #869 hang), not a generic error;timeout(1)is deliberately not used: it does not exist on a stock macOS, and running the VM stack under it wedges the boot (libkruntcsetattr()s fromtimeout's background process group and SIGTTOU stops the group). The hand-rolled watchdog runs the client with</dev/nullfor the same reason — which also putsmin activatein its non-interactive mode, where it neither prompts for the non-VCS upload confirmation (#770) nor waits on a keypress for policy gating.How it hooks into the nightly canary
No workflow change is required, and none is made (
.github/workflows/is frozen and CODEOWNER-gated).The nightly test tier does not discover scripts by convention:
nightly-tests.yml'ssession-e2e-soakjob invokes one entry point explicitly,./scripts/soak-session-e2e.sh 10 "$RUNNER_TEMP/soak-logs". That script is reviewed code, so the hook goes there: after its session-e2e reps it reaps, runs the bulk-upload proof once (withMINVMD_BOOT_LOGpointed into the same log dir that is already uploaded as an artifact), and folds the result into its tally and exit status. It inherits the job's whole VM setup — images, gvproxy,PATH— for free.just bulk-upload [n]runs the same proof locally, andjust soakkeeps nightly parity.One thing a CODEOWNER may need to follow up on: the proof now shares the
session-e2e-soakjob'stimeout-minutes: 60with the ten session-e2e reps. It should add roughly 5–10 minutes (one VM boot plus five activates against a warm cache), but I could not measure it. If that job starts timing out, the fix is a workflow edit I cannot make — either bumptimeout-minutes, or split it into its own job:Either way the failure is legible, and
notifyalready files a tracking issue whensession-e2e-soakfails.Cleanup
A canary that leaks 89 sessions a week is worse than no canary:
min destroy --all --forcebefore the next pass;EXIT/INT/TERMtrap runsdestroy --all --force,stop --force,minvmd stopon VM targets, and removes the fixture, the project dir and the private state dir;XDG_STATE_HOME, so the sweep can never touch a developer's real sessions.Verification
Verified. The script's own logic was exercised end to end against a stub
minonPATH(the real VM was never started — see below):2 passed, 0 failed, exit 0; call trace showsls→activate→destroy <sid>per pass, thendestroy --all --force+stop --force; no temp dirs left in/tmpor$HOMEactivateexits 1#869pointer, diagnostics dumped,0 passed, 2 failed, exit 1activatehangs (deadline 3 s)#869hang, loop stopped with2 iteration(s) not run, exit 1activateprints a non-UUIDRUST_LOG=debug/MINVMD_KRUN_LOG=debugzstd -3 -T1shellcheck scripts/bulk-upload-e2e.sh scripts/soak-session-e2e.shSC2086s that matchsession-e2e.sh's intentionalE2E_MINIMAL_ARGSword-splittingbash -nboth scripts,just --listNot verified — needs a reviewer or CI to run it. Another agent is holding the only VM on this machine for a timing-sensitive measurement, and a second VM would collide on the provider directory, so I could not run this against a real stack. Specifically unproven:
min activateof the 49 MiB project succeeds on a good stack (and, on a pre-fix stack, fails). Reviewer check:just bulk-uploadon a KVM or macOS host. On a stack still carrying #869 it should fail ~89% of iterations with thechannel closedline.scripts/soak-session-e2e.sh's new phase — syntax-checked only; running it invokesscripts/reap-vms.sh, which would have killed the VM I was told not to touch.session-e2e.shalready depends on perl.Refs: #869, gominimal/inbox#335
🤖 Generated with Claude Code
Note
Add canary e2e test for the bulk host-to-guest upload path
min activateiterations, emitting CI annotations and failing if any iteration errors or hangs.just bulk-uploadtask in justfile to invoke the new script with a configurable iteration count (default 5).LOGDIRto use an absolute path and adds error handling for boot-log directory creation.Macroscope summarized 66a09de.
Summary by CodeRabbit