Skip to content

fix(test): keep fixture stubs ahead of the host PATH - #493

Merged
toasterbook88 merged 2 commits into
mainfrom
fix/hw-fixture-path-inherits
Oct 6, 2026
Merged

toasterbook88 merged 2 commits into
mainfrom
fix/hw-fixture-path-inherits

Conversation

@toasterbook88

Copy link
Copy Markdown
Owner

Keep fixture stubs ahead of the host PATH (Hardware Validation is red on main)

Three test fixtures added in #489 replace their child-process PATH with
stubdir:/usr/bin:/bin. The scripts under test call real tools (awk for
the 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 last Hardware Validation pushes
are red with sh: line 1: awk: command not found while the same tests
pass 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.go and cmd/axis/model_observation_test.go keep
    the stub directory first and append the inherited PATH after it, built
    with a single sep := string(os.PathListSeparator); HOME= handling is
    unchanged. Fixture stubs keep shadowing real binaries.
  • internal/facts/remote_bundle_gpu_test.go replaces the manual
    append(os.Environ(), "PATH=…") with the package's existing
    withSandboxedPATH(dir) helper, which already orders stubs first and
    appends profile fallbacks. No new PATH implementation is introduced.

Validation run

  • go vet ./cmd/axis ./internal/facts clean.
  • Focused suites green:
    go test ./cmd/axis -run 'TestShellStopMLX|TestLlamaServerSample' and
    go test ./internal/facts -run 'TestRemoteBundleKeeps|TestResidentModels'.
  • A batched TestModelEvict* run (TestModelEvictDefaultsToLiveSnapshot,
    TestModelEvictLiveFalseUsesDaemonCache, TestModelEvictByGPUIndex,
    TestModelResumeByReceiptID,
    TestModelResumeUnitNameUsesSnapshotSupervisorAndPort) first hit
    event-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.
  • No workflow file is touched: the workflow's Install runner dependencies
    step already places the profile directory on PATH, and the hermetic-test
    harness leaves PATH to the caller.
  • This branch needs one Hardware Validation run on itself to confirm the
    self-hosted lane goes green with the fixture fix in place; local focused
    runs alone do not satisfy that.

Notes

  • Test-only change; no product code touched (3 files, +14/−3).
  • Merging this before the next release tag keeps the hardware lane quiet.

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.
Copilot AI balanced review requested due to automatic review settings October 5, 2026 19:47

@toasterbook88 toasterbook88 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to run its full agentic suite in this review.

Copilot review overview

Review effort: Lite
Findings: 2 Medium severity · 1 Low severity

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 PATH helper.
  • Updated Axis CLI tests to prepend fixture dirs while inheriting the host PATH for utilities like awk/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.

Comment thread cmd/axis/model_mlx_test.go Outdated
Comment thread cmd/axis/model_observation_test.go Outdated
Comment thread cmd/axis/model_mlx_test.go Outdated
toasterbook88 added a commit that referenced this pull request Oct 6, 2026
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.
@toasterbook88
toasterbook88 merged commit 471209c into main Oct 6, 2026
6 checks passed
@toasterbook88
toasterbook88 deleted the fix/hw-fixture-path-inherits branch October 6, 2026 00:27
toasterbook88 added a commit that referenced this pull request Oct 6, 2026
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.
toasterbook88 added a commit that referenced this pull request Oct 6, 2026
…#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.
toasterbook88 added a commit that referenced this pull request Oct 6, 2026
#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants