Repository navigation
fix(test): keep fixture stubs ahead of the host PATH - #493
Conversation
The three fixture helpers introduced with the model run profile (#489) replaced PATH with stubdir:/usr/bin:/bin, so scripts under test lost the host awk on profile-based runners (the self-hosted NixOS runner keeps no awk outside its Nix profile). Stub-first inherited PATH restores the tools while fixture stubs keep shadowing real binaries; the facts side reuses the existing withSandboxedPATH helper. Ref: PR #489 review comment documenting the red Hardware Validation runs (runs 37250216353, 37250828528) and the /usr/bin assumption.
toasterbook88
left a comment
There was a problem hiding this comment.
AXIS Grok gate
Verdict: PASS-WITH-RISK
PR: #493 fix(test): keep fixture stubs ahead of the host PATH
Head: e1f74d4
URL: #493
Event: COMMENT only. No APPROVE.
Files (3, +14/−3, test-only):
- cmd/axis/model_mlx_test.go
- cmd/axis/model_observation_test.go
- internal/facts/remote_bundle_gpu_test.go
Invariant hits: none on the product plane. No fact-plane write, no confirm/dispatch lock, no cache badge change, no secret_read / ssh_bypass / fleet_exec / spawn_subagent / self-authorize / unattended config write. Advisory self-model unchanged. Charter hops still 0.
What the diff does: the three #489 fixtures stopped replacing PATH with stubdir:/usr/bin:/bin. cmd/axis now sets PATH to stubdir + os.PathListSeparator + os.Getenv("PATH") (HOME= unchanged in the observation helper). facts reuses withSandboxedPATH, which already puts the stub dir first, then inherited PATH, then /run/current-system/sw/bin:/usr/bin:/bin, and appends that PATH last so Go's last-duplicate-wins replaces the host PATH entry. Stubs still shadow same-named binaries.
CI at review (2026-10-05T19:47Z): Test & Build queued (run 37365694832), govulncheck queued (run 37365694620), Analyze (actions) queued (run 37365692278), auto-merge skipped. Combined status success is only Devin Review skipped (trial expired). Hardware Validation is not on this head: .github/workflows/hardware-validation.yml is workflow_dispatch + cron only, no pull_request trigger. The cited red runs (37250216353, 37250828528) are not re-run by this PR.
Residuals:
- The lane this claims to fix cannot go green on the PR itself. Confirm with a workflow_dispatch of Hardware Validation on this SHA, or accept that the next main push is the receipt.
- cmd/axis helpers inherit PATH but do not append the Nix profile fallback that withSandboxedPATH adds. They work only if the runner job PATH already contains awk. Empty or Ubuntu-shaped PATH still misses it.
- Inheriting the host PATH is less hermetic: unstubbed names resolve to host binaries. Named stubs stay first, so that is the intended tradeoff, not a product change.
- Author is cranium-agent (agent@local). Diff matches the stated root cause; local focused runs in the body are not a Hardware Validation receipt. Evict timeout note is load-correlated and not a code change.
Operator action: do not treat this as a merge receipt for the red hardware lane. Wait for Test & Build, or dispatch Hardware Validation on e1f74d4. No APPROVE from this gate.
There was a problem hiding this comment.
Note
Copilot was unable to run its full agentic suite in this review.
Copilot review overview
Review effort: Lite
Findings: 2
Open (3)
What changed in this PR
Adjusts test command environments to use a more robust PATH setup so fixtures (stubs) remain first while still working on profile-based runners (e.g., NixOS).
Changes:
- Switched one GPU bundle test to a shared sandboxed
PATHhelper. - Updated Axis CLI tests to prepend fixture dirs while inheriting the host
PATHfor utilities likeawk/base64.
| File | Description |
|---|---|
| internal/facts/remote_bundle_gpu_test.go | Replaces hand-built PATH env with package helper for consistent sandbox behavior across runners. |
| cmd/axis/model_observation_test.go | Updates shell runner PATH to include inherited host PATH while keeping fixture stubs first. |
| cmd/axis/model_mlx_test.go | Same PATH inheritance approach for mlx test harness, keeping stubs first. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Addresses the #493 review thread about duplicated PATH construction. b32c582 appended /usr/bin:/bin in both fixtures inline; this replaces the two copies with stubFirstPATH(dir), mirroring withSandboxedPATH in internal/facts: stubs first, the inherited PATH (skipped when empty so no empty element / current directory leaks in), then /run/current-system/sw/bin, /usr/bin, /bin. E2E: with a parent PATH holding only go, sh and sleep, TestShellStopMLXGuardMatchesServerNotPythonOrConsole and TestLlamaServerSampleScriptMaxRSSAndDeviceRow failed on e1f74d4 with "awk: not found" and pass with this helper.
Rebuilt on main after #493 (which inlined dir:$PATH:/usr/bin:/bin). Addresses the #498 review threads: an empty-PATH fallback is useless if exec.Command resolves the fixture's own sh/sleep from the parent PATH before cmd.Env applies. - stubFirstDirs(dir): stubs, inherited PATH entries (empty elements dropped), /run/current-system/sw/bin, /usr/bin, /bin. - stubFirstPATH(dir): the child PATH built from that list. - stubFirstCommand(dir, name, ...): resolves name from the same list and sets the child PATH; used for runSampleShell's sh and runMLXStopScript's sleep (the stop script already runs /bin/sh). - hack/e2e-fixture-empty-path.sh: committed E2E harness that compiles the cmd/axis test binary and runs both fixture tests under `env -i PATH=`. On main (471209c) both fail with "sleep"/"sh": executable file not found; with this change both pass.
…#493 review) (#498) Rebuilt on `main` (471209c) after #493 merged. Test-only change, plus one committed E2E harness script. **Problem:** #493 inlined `dir:$PATH:/usr/bin:/bin` in two fixtures, but the fixture's own interpreter (`sh` in `runSampleShell`, `sleep` in `runMLXStopScript`) is resolved by `exec.Command` from the *parent* PATH before `cmd.Env` applies. With an empty PATH, the fallback directories are never reached. **Change:** - `stubFirstDirs(dir)`: stubs, then inherited PATH entries (empty elements dropped, so the current directory never leaks in), then `/run/current-system/sw/bin`, `/usr/bin`, `/bin`. - `stubFirstPATH(dir)`: the child PATH built from that list. - `stubFirstCommand(dir, name, ...)`: resolves `name` from the same list and sets the child PATH. Used for `sh` and `sleep`. - `hack/e2e-fixture-empty-path.sh`: compiles the `cmd/axis` test binary and runs both fixture tests under `env -i PATH=`. **E2E evidence** (empty PATH, compiled test binary): - main 471209c: `exec: "sleep": executable file not found` and `exec: "sh": executable file not found`, so both tests fail - this head: both pass **Local gates (go1.26.6):** deadcode passes; `go vet` clean; `gofmt -l .` empty; staticcheck clean; `verify-repo-truth` and `verify-public-boundary` pass. `hermetic-go-test.sh ./cmd/axis/... ./internal/facts/...` is ok except the known pre-existing box failure `TestProbeAdvisoryLockHeldDoesNotConfuseFileWithLock`. Not merging. This is for review only.
#494 (v0.19.5 release prep) merged at 2026-10-06 01:01 UTC, before the rest of the v0.19.5 merge chain. Its CHANGELOG section listed #476–#493 only. This PR adds the PRs that landed on `main` after it, so the v0.19.5 section matches what will be tagged: - #495 self-update download hardening (includes #496 and #499, applied to #495's branch before its squash) - #497 daemon restart after update; `axis version` shows the running daemon - #500 `model stop --live`, failed-unload receipts, MLX refusals - #501 keep `[N/A]` nvidia-smi rows - #498 stub-first fixture PATH helper The intro line is updated too (23 PRs, plus #494 itself). Docs-only change: `internal/buildinfo/version.go` stays `0.19.5`, and the section date `2026-10-05` is left as #494 set it. **Local checks:** `hack/verify-repo-truth.sh` OK; `hack/verify-doc-facts.sh` OK; `hack/validate-release-version.sh --source` → v0.19.5, and `validate-release-version.sh v0.19.5` → exit 0.


Keep fixture stubs ahead of the host PATH (Hardware Validation is red on main)
Three test fixtures added in #489 replace their child-process
PATHwithstubdir:/usr/bin:/bin. The scripts under test call real tools (awkforthe nvidia-smi bundle parse and the classifier/observation checks), and on
profile-based Linux runners those tools live only in the system profile —
not under
/usr/bin//bin. Result: the lastHardware Validationpushesare red with
sh: line 1: awk: command not foundwhile the same testspass on conventional hosts and locally (run/job refs: 37250216353,
37250828528, jobs 111576154161, 111577904135). Full root-cause comment is
on the source PR (#489).
Change
cmd/axis/model_mlx_test.goandcmd/axis/model_observation_test.gokeepthe stub directory first and append the inherited PATH after it, built
with a single
sep := string(os.PathListSeparator);HOME=handling isunchanged. Fixture stubs keep shadowing real binaries.
internal/facts/remote_bundle_gpu_test.goreplaces the manualappend(os.Environ(), "PATH=…")with the package's existingwithSandboxedPATH(dir)helper, which already orders stubs first andappends profile fallbacks. No new PATH implementation is introduced.
Validation run
go vet ./cmd/axis ./internal/factsclean.go test ./cmd/axis -run 'TestShellStopMLX|TestLlamaServerSample'andgo test ./internal/facts -run 'TestRemoteBundleKeeps|TestResidentModels'.TestModelEvict*run (TestModelEvictDefaultsToLiveSnapshot,TestModelEvictLiveFalseUsesDaemonCache,TestModelEvictByGPUIndex,TestModelResumeByReceiptID,TestModelResumeUnitNameUsesSnapshotSupervisorAndPort) first hitevent-flush 5-second timeouts during a heavy-load window (workstation
load average 116–160). A later batch re-run of the same five tests
passed on this tree, and the same batched set passed on a pristine
main-tip tree. No controlled load A/B was performed, so the timeouts
are load-correlated with causality unproven; no test code was changed.
Install runner dependenciesstep already places the profile directory on PATH, and the hermetic-test
harness leaves PATH to the caller.
Hardware Validationrun on itself to confirm theself-hosted lane goes green with the fixture fix in place; local focused
runs alone do not satisfy that.
Notes