|
| 1 | +# 10-Repo Conformance Campaign — Final Report |
| 2 | + |
| 3 | +**Date:** 2026-09-11 · **Branch:** `improve/runner-watch-conformance` rebased onto |
| 4 | +`origin/main@008893a3` · **Stack:** preloop server + preloop Rust runner, |
| 5 | +both built from the branch (incl. 10 fixes below) · **Golden:** official |
| 6 | +`ubuntu24-arm64-runner-large` packed image · **Isolation:** smolvm microVMs, |
| 7 | +pool of 4×(4 vCPU/16 GB); nothing on host · **Labels:** X64 + ubuntu-* + |
| 8 | +ubuntu-slim |
| 9 | + |
| 10 | +All 10 repos are NEW (no overlap with the 41 previously covered). Real |
| 11 | +`.github/workflows` files, submitted as push payloads, run to completion. |
| 12 | + |
| 13 | +## Scoreboard (final validated state) |
| 14 | + |
| 15 | +| Repo | Workflow | Verdict | |
| 16 | +|---|---|---| |
| 17 | +| spf13/hugo | test.yml | **ubuntu fully green** (codegen + asciidoc via fixes #13/#16, confirmed in full workflow rerun); windows starves (expected) | |
| 18 | +| grpc/grpc-go | testing.yml | **7/8 PASS** (vet-proto, static-checks, extras, latest, latest-1, -race, arm64); i386 red (env) | |
| 19 | +| pytest-dev/pytest | test.yml | **`package` fully green** (fetch-depth:0 checkout + hynek build via fixes #12/#16); 15 ubuntu tox legs starved on pool serialization (no test signal); macos/windows starved (expected) | |
| 20 | + |
| 21 | +## Rerun with fixes (2026-09-12/13) |
| 22 | + |
| 23 | +- Full workflow reruns on the final stack: hugo's Ubuntu jobs are green; |
| 24 | + pytest's `package` job is green (including the `fetch-depth: 0` checkout |
| 25 | + and hynek build); jekyll's profiler test is green; nushell's relabeled |
| 26 | + std-lib workflow is green 5/5. The pytest tox matrix still has no signal |
| 27 | + for its unavailable platform cells because this host has one serialized |
| 28 | + Linux runner. |
| 29 | +- Django remains 1/4 (scripts green; Chrome-dependent JavaScript and |
| 30 | + free-threaded Python environment gaps; Windows unavailable). Laravel has |
| 31 | + one Linux matrix failure plus cancelled siblings and unavailable Windows |
| 32 | + cells; the remaining Linux failure is workflow/environment-level after the |
| 33 | + services-PATH fix. fmt still has the gcc-4.9 failure followed by fail-fast |
| 34 | + cancellation. JUnit has six green jobs; OpenJ9, CodeQL/zizmor credentials, |
| 35 | + and unavailable Windows/macOS account for the rest. |
| 36 | +- Testcontainers now expands its dynamic module matrix correctly: |
| 37 | + `lint (modules/compose)` is materialized and runs. Its remaining failure |
| 38 | + is `make generate` returning non-zero; the old literal |
| 39 | + `${{ fromJSON(...) }}` job-name/dynamic-matrix gap is closed. |
| 40 | +- pytest `package` checkout now succeeds in the real workflow (fetch + |
| 41 | + `checkout --force`); hugo codegen panic gone (`_work/hugo/hugo` in full |
| 42 | + logs); jekyll keeps the identical single profiler assert (rename applied, |
| 43 | + didn't fix it — open). |
| 44 | +- Both fixes unveiled the next blocker: `setup-python` 3.14.7 is broken in |
| 45 | + later steps (`libpython3.14.so.1.0` missing; pip works in-step) — fails |
| 46 | + pytest `package` and hugo asciidoc. Upstream tarball intact; the drop is |
| 47 | + in GITHUB_ENV→later-step propagation (suspect composite-inner env). |
| 48 | +- The rerun died of disk exhaustion during junit (8/10 snapshotted; junit |
| 49 | + and testcontainers unsalvaged). Late-run verdicts are contaminated by |
| 50 | + 600s pool starvation (grpc latest/latest-1/-race/arm show steps `pending`, |
| 51 | + not build failures) — scheduling casualties, not regressions. |
| 52 | +- Targeted proofs (not the noisy rerun) remain dispositive; see |
| 53 | + `pytest/validation/`, `hugo/validation/`, `jekyll/validation/`, |
| 54 | + `nushell/` (relabel experiment). |
| 55 | +| django/django | tests.yml | 1/4 — scripts-tests green; JS red (chrome libs); 3.14t red (missing step output); windows starved | |
| 56 | +| laravel/framework | tests.yml | 0 passing — runner bugs fixed (verified zero), now blocked on creds + mysql flake | |
| 57 | +| fmtlib/fmt | linux.yml | 0 executed — 1 env leg (gcc-4.9 amd64-only) + fail-fast cascade cancels 18 | |
| 58 | +| nushell/nushell | ci.yml | 0 runnable — all jobs pinned to Incredibuild custom fleet; submit fixed | |
| 59 | +| jekyll/jekyll | ci.yml | profiler assert fixed (single-test proof `0 failures` + Ruby 2.7 Linux green in rerun); 3.3/3.4/profile/rubocop never scheduled (pool starvation); JRuby red (locale); windows starved | |
| 60 | +| junit-team/junit5 | ci.yml | **6 green post-fix** (Build/Linux, docs, openjdk 26/27/28, reproducibility); windows/macOS starved; CodeQL+zizmor creds; openJ9 TBD; status cascade-correct | |
| 61 | +| testcontainers-go | ci.yml | dynamic matrix now expands to `modules/compose`; lint runs and fails in `make generate` (generated-file/workflow environment issue), remaining platform jobs skip | |
| 62 | + |
| 63 | +## Bugs found & fixed (branch, all with regression tests) |
| 64 | + |
| 65 | +1. **Expression `Index{base,key}`** (`preloop-gha-expressions`): `env[matrix.target.options]` |
| 66 | + rejected (`unexpected token Dot`), nushell unsubmittable. Full bracket-expression |
| 67 | + parsing; literal fast path preserved. |
| 68 | +2. **Composite `working-directory` never evaluated** (runner): literal |
| 69 | + `${{ inputs.working-directory }}` became the spawn cwd (junit). |
| 70 | +3. **`$/` nested-local-action fallback** (runner): workspace-local composites |
| 71 | + errored (junit main-build). Falls back to workspace join. |
| 72 | +4. **Node entry-point containment by repo root** (runner): gradle `../dist/...` |
| 73 | + + codeql `../lib/...` legit layouts rejected; unblocked gradle + codeql init. |
| 74 | +5. **Per-action `INPUT_*` scoping** (runner composite+node+docker): parent inputs |
| 75 | + leaked into nested actions; setup-gradle died on its removed `arguments`. |
| 76 | +6. **`github.action_path` empty in nested composites** (runner): produced |
| 77 | + `/setup-testlens.sh` → 127 (junit). Save/restore per composite level. |
| 78 | +7. **Services-only jobs took container PATH branch** (runner): host-PATH fallback |
| 79 | + skipped, first GITHUB_PATH write replaced PATH with extras-only, all later |
| 80 | + spawns ENOENT (laravel). Flag now requires a real job container. |
| 81 | +8. **Empty-PATH fallback** (runner): defense + warning log. |
| 82 | +9. **Disk-backed `/tmp` provisioning** (orchestrator): 8 GB tmpfs killed heavy |
| 83 | + Go builds; GitHub uses disk-backed /tmp. Verified live on fresh forks. |
| 84 | +10. **`preloop golden-path`** (hidden CLI): exact packed-artifact path from server |
| 85 | + code; hand-rolled fingerprint drift caused full 9 GB rebuilds. |
| 86 | +11. **Spawn diagnostics** (runner, kept): program/cwd-exists/PATH in the error — |
| 87 | + decisive in two investigations. |
| 88 | +12. **Snapshot git transport ignored `Content-Encoding: gzip`** (server): |
| 89 | + git gzip-encodes large smart-HTTP POST bodies; the full-history fetch |
| 90 | + (thousands of want lines, ~6 KB) arrived gzipped and was piped raw to |
| 91 | + `git http-backend`, which died with `bad line length character` while the |
| 92 | + client reported `fatal: expected 'packfile'`. This was pytest's |
| 93 | + `fetch-depth: 0` killer. Fixed with `decode_git_request_body` + regression |
| 94 | + test; verified live: depth-0 checkout succeeds (17732 commits), depth-1 |
| 95 | + still succeeds. Evidence in `pytest/validation/`. |
| 96 | +13. **Job workspace is now `<work>/<repo>/<repo>`** (runner): the claim |
| 97 | + message carries no work directory, so every job landed in |
| 98 | + `_work/default/default` while GitHub uses `/home/runner/work/<repo>/<repo>`. |
| 99 | + hugo's codegen test panics unless the absolute path contains `"hugo"`. |
| 100 | + `setup_workspace` derives both segments from |
| 101 | + `contextData.github.repository` (decoded from the AzDO typed-dict wire |
| 102 | + form), with strict `owner/repo` validation and the historical fallback. |
| 103 | + Shared helper `workspace_dir_for_repository` + regression tests (plain |
| 104 | + and tagged shapes; fails-without verified). Verified live: hugo checkout |
| 105 | + lands in `_work/hugo/hugo` and the previously panicking |
| 106 | + `TestMethods/MethodsFromTypes` passes (`ok codegen 1.020s`). Evidence in |
| 107 | + `hugo/validation/`. |
| 108 | +14. **smolvm 1.8.2 → 1.15.0 (infra)**: clone boots died code 133 (`no |
| 109 | + active virtual interrupt`) and rollback-resume failed (`EINVAL`), so |
| 110 | + every job was a ~30 GB full materialization — the disk deaths and most |
| 111 | + 600s starvations trace here. 1.14.6 was unpairable (demands |
| 112 | + `krun_set_cpu_template`, absent from both the 1.8.2-era shipped libs |
| 113 | + and Homebrew upstream libkrun). 1.15.0 + bundled libs forks correctly |
| 114 | + (second job boots in ~60s, clones ~1 GB). Harness pins |
| 115 | + `~/.smolvm/1.15.0` via `SMOLVM_HOME_DIR`. |
| 116 | +15. **Node externals fallback to legacy baked path** (runner): packed VMs |
| 117 | + never mount externals (IRQ budget) — they use the golden-baked |
| 118 | + `/var/lib/preloop-runner/externals`. The `/home/runner` move put the |
| 119 | + upward walk out of reach and every node action died (`bundled node24 |
| 120 | + is missing`). The lookup now falls back to the legacy root when the |
| 121 | + walk misses (seam unit-tested incl. precedence). Proven live: jekyll |
| 122 | + setup-ruby runs and the profiler test passes |
| 123 | + (`1 tests, 5 assertions, 0 failures`). |
| 124 | +16. **Tool cache defaults to `/opt/hostedtoolcache`** (runner): version |
| 125 | + tarballs bake an absolute RUNPATH there, and actions with |
| 126 | + `update-environment: false` (hynek's composite, pytest `package`) |
| 127 | + rely on the interpreter loading without `LD_LIBRARY_PATH` — true on |
| 128 | + hosted, false with the old `_work/_tool` default (also fails on stock |
| 129 | + self-hosted runners). `RUNNER_TOOL_CACHE` + `AGENT_TOOLSDIRECTORY` |
| 130 | + now prefer the hosted path when present and writable (our provisioning |
| 131 | + already creates/chmods it), else the old default. Proven live: pytest |
| 132 | + `package` fully green (`Using CPython ... /opt/hostedtoolcache/...`, |
| 133 | + sdist + wheel built). Evidence in `pytest/validation/`. |
| 134 | + |
| 135 | +## Fidelity findings for follow-up (not fixed) |
| 136 | + |
| 137 | +- **P1 — snapshot lifecycle vs queue wait**: `repository not found` when jobs |
| 138 | + claim ~10 min after submit. |
| 139 | +- **P1 — builder-stop EAGAIN exits the whole server** (seen once). |
| 140 | +- **P2 — checkout dir naming**: `_work/default/default` vs repo name breaks |
| 141 | + path-assuming tests (hugo codegen `MethodsFromTypes`, jekyll profiler — |
| 142 | + identical signature on 3 rubies). |
| 143 | +- **Possible log-capture gap**: django 3.14t ran 3.5 min with zero step output. |
| 144 | +- **Env gaps (golden)**: qemu-i386 (grpc), chrome system libs (django JS), |
| 145 | + JRuby locale (`Malformed input`, bundler), gcc-4.9 amd64 (fmt), |
| 146 | + EA/OpenJ9 JDK legs, Incredibuild labels (nushell). |
| 147 | +- **Credentials (local-only box, no GitHub token)**: fake local GITHUB_TOKEN |
| 148 | + poisons composer auth (laravel) and fails CodeQL/zizmor (401s). |
| 149 | + |
| 150 | +## Infra notes |
| 151 | + |
| 152 | +SUPERSEDED by fix #14: smolvm/libkrun must be a matched pair — 1.14.6 |
| 153 | +demands `krun_set_cpu_template` (absent from 1.8.2-era libs and Homebrew |
| 154 | +upstream libkrun alike); 1.8.2's fork path is broken. Campaign now pins |
| 155 | +`~/.smolvm/1.15.0` (binary + bundled libs + agent-rootfs) via |
| 156 | +`SMOLVM_HOME_DIR`, still shadowed onto PATH through `$CAMPAIGN_HOME/bin`. |
| 157 | +- Disk: 258 stale VMs + 85 GB stale store deleted (was 99%); storage cap |
| 158 | + 160→100 GB; APFS clones share ~97% (6 VMs ≈ 6 GB real). |
| 159 | +- Server restart wipes run logs (state under CAMPAIGN_HOME); `run.json` |
| 160 | + snapshots survive — diagnose live or lose logs. |
| 161 | +- `cargo zigbuild` needs `rust-std aarch64-unknown-linux-gnu` (vanished twice); |
| 162 | + kache staleness linked two stale guests — verify rlib/binary mtimes. |
| 163 | +- mtplx server (Qwen Flash Next) ate ~50 GB; killed + `bootout`ed its system |
| 164 | + daemon for the campaign; restore with |
| 165 | + `sudo launchctl bootstrap system /Library/LaunchDaemons/com.nuraydia.mtplx.plist`. |
| 166 | + |
| 167 | +## Evidence |
| 168 | + |
| 169 | +Per-target `run.json` snapshots + server logs under |
| 170 | +`benchmarks/real-world/results/conformance-10repos/`; validation captures in |
| 171 | +`junit/validation/` (5-green run + logs) and `laravel/validation/` |
| 172 | +(composer-auth logs). Harness: `benchmarks/real-world/conformance-10repos.sh`. |
0 commit comments