ci: run the bootstrap from pwsh and git bash - #307
Merged
Conversation
gg.cmd always ends up in cmd.exe, but it inherits the PATH and PSModulePath of whatever shell you started it from, and that is where #291 and #292 lived - Git's GNU tar sitting ahead of the System32 one, and PowerShell 7 shadowing the module Get-FileHash is defined in. Both only bite on the first run, and both shipped. Nothing in CI was ever that parent. test-batch starts from cmd, and test-OS1 says `sh gg.cmd`, which takes the shell branch and never touches the bat one. So this adds a job per parent shell that wipes the cache and runs ./gg.cmd from git bash and .\gg.cmd from pwsh - no `sh`, since handing a .cmd to cmd.exe is the point. Only runs where the secret is, or there is no stage4 blob to bootstrap from, so the gatekeeper takes skipped for this one job. The pwsh half is a real regression test - that mechanism reproduces every time. The git bash half is weaker: GNU tar coped on the VM I reproduced #291 on, so it guards the environment rather than that exact bug.
Every other test job runs its command twice, and this one did not - so the cached branch at the top of stage1.bat, which is what every run after the first takes, was never reached. Two lines. pwsh does not stop on a failing native command, only on cmdlet errors, so the cold run gets its exit code checked before the warm one can paper over it. Bash is already -e. Also fixes the comment on the wipe: %UserProfile% and $HOME both land on C:\Users\runneradmin on a runner, and the VM is fresh anyway, so it is belt and braces rather than the two different places I claimed. And bash does not hand the .cmd to cmd.exe itself - MSYS does that by extension.
Two holes, both found by review rather than by the job failing. The job asserted an exit code, and stage2.ps1 hands back 0 when it never got a process at all - Start-Process runs with -ErrorAction SilentlyContinue and the catch below it defaults $exitCode to 0. So gg could have launched nothing and the step would still pass. Both legs now check what node printed, the way the #289 test already does. And the job skipped itself without the Azure secret, with the gatekeeper told that skipping was fine. Fork PRs therefore got no parent-shell coverage at all and a green tick. It now seeds stage4 from the artifact like the other jobs do, so it always runs: stage1 still unpacks, which is where #291 lives, and only the blob download drops out. The gatekeeper is strict again. The cache wipe went too. It deleted a directory that cannot exist on a fresh VM, and claimed %UserProfile% and $HOME were different places, which they are not on a runner. What actually matters is that stage2.ps1 is absent, since that is what stage1.bat keys off - so remove that and assert it, and the job fails loudly if anything ever seeds a full cache.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
gg.cmdalways ends up executing throughcmd.exeon Windows, but it inherits thePATHandPSModulePathof whatever shell you started it from. Two bugs shipped through that seam this week:C:\Program Files\Git\usr\binsits ahead ofSystem32, so a baretarresolved to Git's GNU tar instead of bsdtar. Unpacking died.PSModulePathand loads its Core-onlyMicrosoft.PowerShell.Utility. Binary cmdlets kept working;Get-FileHashvanished.Neither was caught, and not for the reason I first assumed. The cold path was already tested —
has_secretis true on same-repo PRs, so "Pre-populate cache with stage4" is skipped there. The actual gap is the parent shell:test-OS1bashsh gg.cmdshis explicit, cmd.exe never sees ittest-batchcmd.\gg.cmdcmd.exeSo the batch half was covered and Git Bash was covered, but never Git Bash as parent of the batch half. Both bugs live in that cell.
What this adds
test-parent-shells: one job per parent shell onwindows-2022, running.\gg.cmdfrom pwsh and./gg.cmdfrom Git Bash — deliberately notsh gg.cmd, since MSYS dispatching a.cmdtocmd.exeby extension is the whole point.Each leg runs twice (cold, then the cached branch at
stage1.bat:15, which nothing else covered) and asserts what node printed, not just the exit code —stage2.ps1runsStart-Process -ErrorAction SilentlyContinueand its catch defaults$exitCode = 0, so gg can exit 0 having launched nothing.The job always runs. When the Azure secret is absent it seeds stage4 from the artifact like the sibling jobs, so fork PRs still cover stage1's unpack — where #291 lives — with only the blob download dropping out. The gatekeeper is strict; no skip exemption.
Verified
Dispatched on real runners: 178 jobs green, 0 failures. Both legs cold-bootstrapped (
node not found in cache→ download →v26.5.0), then ran warm. The Git Bash leg's own PATH dump confirms the hazard is live there:actionlintreports the same findings asmain(7 pre-existing shellcheck items) — nothing new.Honest limits
Get-FileHashand it goes red deterministically. The bash leg is weaker: it proves Git's GNU tar is first onPATH, but with theSystem32pin in place GNU tar is never invoked, so reverting the pin might leave it green. It guards the environment more than that specific bug.timeout-minutes(the job hits the Azure blob cold every run, andstage2.ps1:92has no-TimeoutSecor retry), and preconditions asserting the job still reproduces its own premise — Git's tar still shadowingSystem32, pwsh still ≥ 7.