GitHub Actions trigger and shell gotchas
Two recurring CI pitfalls showed up during assethold issue execution (#2448 follow-up after #2442):
1. on.push.paths can silently suppress the verification run
A valid fix may land on main without triggering the workflow you need for proof.
Observed case:
- deleting pathological
.csvtree entries fixed a Windows checkout blocker - but
Python Testsdid not auto-run because the workflow only watched:**/*.pyrequirements*.txtpyproject.tomluv.tomlsetup.pysetup.cfg- the workflow file itself
- since the change touched only
.csvpaths, there was no automatic CI run
Operational rule:
- Before relying on post-push CI evidence, inspect the workflow's
on.push.pathsfilters. - If your fix does not match those paths, do NOT assume "CI did not run" means the fix failed.
- Use one of these instead:
gh workflow run <workflow-name> --ref mainwhenworkflow_dispatchexists- or a deliberate verification commit that touches a watched path
- In the issue closeout comment, state explicitly that verification used a manual dispatch rather than a push-triggered run.
Recommended command:
gh workflow run 'Python Tests' --repo OWNER/REPO --ref main
gh run list --repo OWNER/REPO --workflow 'Python Tests' --limit 3
2. Bash-style multiline run: steps can break on Windows PowerShell
Observed case after moving smoke before lint:
run: |
pytest tests/test_smoke.py \
--verbose \
--tb=short
This worked on Linux/macOS but failed on Windows PowerShell with:
Missing expression after unary operator '--'
Root cause:
- GitHub Actions runs PowerShell by default on Windows
- Bash line continuations (
\) are not PowerShell syntax - a step that was previously unreachable can start failing the moment you reorder the workflow to make it reachable
Operational rule:
- For cross-platform steps, prefer a shell-neutral single-line command when possible:
run: pytest tests/test_smoke.py --verbose --tb=short
- Use explicit
shell: bashonly when Bash is intentional and guaranteed on that runner. - After reordering workflow steps, re-check shell compatibility on every OS in the matrix, not just the originally failing OS.
3. Repeated PR check failures can be shared CI environment drift, not branch regressions
Observed during post-reboot salvage of already-pushed workspace-hub branches:
- two independent PRs from unrelated branches had local targeted validation green and GitHub
Run Tests/Code Qualitygreen - both failed the same auxiliary checks:
Stage Prompt Drift Guardfailed before real drift analysis withModuleNotFoundError: No module named 'workspace_hub'Review Evidence Checkfailed before review evaluation withscripts/enforcement/require-review-on-push.sh: line 19: uv: command not found
Operational rule:
- When multiple independent PRs show the same failure, inspect check logs before assuming the branch diff is bad:
gh pr checks <PR> --repo OWNER/REPO --watch=false
gh run view <RUN_ID> --repo OWNER/REPO --job <JOB_ID> --log | tail -120
- Classify whether the failure reached the intended validator or failed during setup/import/tool discovery.
- If it failed during setup, compare against another independent PR/branch. Identical setup failures across branches are usually CI harness drift.
- Preserve branch evidence by commenting on the PR with:
- local validation already run
- which checks are green
- exact shared failing checks and first setup error
- a linked follow-up issue for the CI harness repair
- Do not merge solely because the failure is shared infra; keep the PR parked until required review/merge policy is satisfied or the CI harness issue is repaired.
- Create a separate planning-required issue for the harness repair instead of absorbing it into an unrelated feature/fix PR.
Common setup-drift signatures:
uv: command not foundin a check that invokes a repo script expectinguvModuleNotFoundErrorfor the repo's own package after an install step, indicating package layout/PYTHONPATH/install-mode mismatch- post-checkout cleanup warnings from malformed submodule/worktree paths; record them if they may hide future failures, but separate them from the primary setup failure
Minimal reusable playbook
When fixing a workflow that should produce a new validation run:
- Inspect
on.push.pathsbefore pushing. - If your changed files are outside the filter, plan a manual
workflow_dispatchverification. - If you expose a previously unreachable step, inspect its shell syntax across OSes.
- Prefer shell-neutral one-line commands for matrix jobs unless there is a strong reason not to.
- If PR checks fail, inspect logs and determine whether the intended validator actually ran.
- If identical setup failures appear across independent PRs, create/link a CI-harness issue and preserve branch evidence in PR comments.
- Record in the issue comment whether verification came from:
- push-triggered run
- manual dispatch run
- both
- local targeted validation plus blocked shared CI harness checks
Why this matters
Without these checks, you can easily misdiagnose:
- "the fix didn't trigger CI" when the workflow filter suppressed the run
- "Windows is still broken" when the original blocker is fixed and a new shell-specific failure is simply the next exposed layer