Skip to content

ci(gate-only): chain the push gate off Unit Tests instead of re-running it - #731

Merged
JarryShaw merged 1 commit into
mainfrom
ci-gate-workflow-run-715
Sep 24, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
ci-gate-workflow-run-715

Conversation

@JarryShaw

@JarryShaw JarryShaw commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

What is the purpose of your pull request?

  • ci — workflows or build tooling

Description

Part of #715. Item 2: deploy-pages, cron-vendor and cron-conda each called unit-tests.yml
with gate-only: true on push: [main], re-running the single-version Gate job against a commit
Unit Tests' push trigger had already tested in full. Measured on run 35890190308 (the issue's
own cited "quiet queue" push): the three Gate jobs took 25m15s, 29m34s, 18m50s -- ~74 of a real
~99 min of duplicate Gate execution that push (create-release.yml's own Gate chains off
Vendor Update already and is out of scope).

Replaced their push trigger with workflow_run on Unit Tests completion, gated per job on
conclusion == 'success' && event == 'push' && head_branch == 'main', since workflow_run also
fires for a pull request's Unit Tests run. schedule triggers keep their own gate.

CodeQL findings and how they were resolved (owner ruling: fix structurally, no false-positive
argument): checking out github.event.workflow_run.head_sha was flagged as "checkout of untrusted
code in a privileged context" on all three files, since that field is a fork's commit on a
PR-triggered workflow_run. Fixed by checking out the default github.sha (main's tip) instead --
all three jobs want main's current tip anyway, and vendor/conda rebase onto it before pushing
regardless. Two "Code injection" alerts on conda-tag's tag/push steps (splicing
needs.conda-update.outputs.* into run: text) are fixed via env: + shell variables. Those two
statements pre-date this PR; the alerts do not -- main's cron-conda.yml had no
attacker-reachable trigger for CodeQL's model until workflow_run introduced one, so "pre-existing"
would understate what changed. Also hardened: cron-vendor.yml gets a default-deny top-level
permissions: {} (it had none), and both maintenance checkouts get persist-credentials: false.

Job-count/duplicate-execution savings are unaffected by the head_sha -> github.sha change: that
saving is entirely from removing the three re-run Gate jobs, unrelated to which ref the downstream
job later checks out.

No Python changed; no coverage delta.

@JarryShaw JarryShaw added ci Pull requests that change CI or workflow configuration (ci: subject prefix) perf Pull requests that improve performance (perf: subject prefix) labels Sep 24, 2026
@JarryShaw
JarryShaw force-pushed the ci-gate-workflow-run-715 branch from ec728aa to 40097d5 Compare September 24, 2026 02:17
…ng it

deploy-pages, cron-vendor and cron-conda each called unit-tests.yml with
gate-only: true on push to main, re-running its single-version "Gate" job
against a commit Unit Tests' own push trigger had already tested in full.
Measured on run 35890190308 (the issue's own "quiet queue" push): the three
in-scope Gate jobs ran 25m15s, 29m34s and 18m50s -- ~74 min of duplicate
execution on that one push alone.

Replace their push trigger with workflow_run on Unit Tests' completion,
gated per job on conclusion == success, event == push and head_branch ==
main -- workflow_run also fires for a pull request's Unit Tests run, and
without this a green PR would deploy docs, push a vendor commit or upload a
conda build from that PR's tree.

Checkout stays on the default github.sha (main's tip) rather than pinning
workflow_run.head_sha: CodeQL flagged the latter as checkout of untrusted
code in a privileged context, since that field is a fork's commit on a
PR-derived workflow_run. All three jobs want main's current tip anyway, and
vendor/conda both rebase onto it before pushing regardless. Also routed
conda-tag's PCAPKIT_VERSION/PCAPKIT_BUILD through env: rather than splicing
them into the script text, closing the code-injection CodeQL flagged there --
that statement pre-existed, but workflow_run is what made it reachable.

Each workflow's schedule trigger keeps its own gate-only call unchanged.
create-release.yml's gate-only caller is untouched: it fires on tag pushes
and Vendor Update's completion, not on every push to main.

Part of #715. No code path changed; no coverage delta.
@JarryShaw
JarryShaw force-pushed the ci-gate-workflow-run-715 branch from 40097d5 to e0f023d Compare September 24, 2026 02:30
@JarryShaw JarryShaw added the review: pending No verdict for the current head - never reviewed, or the head moved since the last one label Sep 24, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

✅ GOOD TO MERGE @ e0f023d31 — traced all three triggers end-to-end: no main push loses its gate (unit-tests.yml has push: branches: [main] with no paths: filter, so its own push run is the verdict for every push, docs-only included), all three consumers act only on conclusion=='success' && event=='push' && head_branch=='main', and the github.sha-over-head_sha divergence is benign-to-preferable for all three — the owner's documented call, and I found it strictly safer (tip-at-event-time cannot deploy out of order; head_sha could).

@JarryShaw

Copy link
Copy Markdown
Owner Author

✅ GOOD TO MERGE @ e0f023d31 — traced all three triggers end-to-end: no main push loses its gate, consumers act only on conclusion=='success' && event=='push' && head_branch=='main', and github.sha-over-head_sha is benign-to-preferable for all three (owner's documented call).

Method: read/traced only — I cannot execute Actions. All three YAMLs parse (PyYAML 6.0.3); actionlint/yamllint were not available on this host.

Claim Verdict Evidence I obtained
Gate exactly once per push, at a sha on main ✅ unit-tests.yml push trigger is branches: [main], no paths: filter; the push run (full matrix + integration + changelog) is stronger than the old single-version gate; the three callers' gate jobs skip on workflow_run and still run on Saturday's schedule
Guards sound ✅ event=='push' can only be true for in-repo pushes (a fork's push runs the fork's workflows; base-repo PR runs are event=='pull_request'), so head_branch=='main' is belt-and-braces on top
github.sha vs head_sha ✅ per consumer docs want the tip; both crons already git pull --rebase onto the true tip before the SSH push (pre-existing), so the "update lands on a newer tip" window predates this PR; the bot's bump push then earns its own full verdict — verified on 5d54eb673, which has its own push-event Unit Tests/CodeQL runs
Ordering kept ✅ Deploy step's if: != 'pull_request' still fires on workflow_run; publish is blocked unless the workflow_run concluded success (push path) or the local gate passed (schedule path)
cron-conda.yml:234 ref: interpolation ✅ safe, pre-existing (0 diff lines touch it) values derive from main's own tree (pcapkit.__version__, conda/build) inside the guarded job, never from the workflow_run payload; checkout ref: is an action input, not a shell sink; the literal conda- prefix precludes option injection
Hardening ✅ both maintenance pushes re-point origin to SSH and authenticate via PYPCAPKIT_ACTIONS_DEPLOY_KEY (ssh-agent); nothing reads a persisted checkout credential, so persist-credentials: false breaks nothing; pages deploy receives GITHUB_TOKEN as an explicit input
CodeQL ✅ Analyze + CodeQL both pass on e0f023d31, 0 annotations

Saving, derived independently: ~60–84 min of ubuntu runner time per fully-executed main push (the 3 removed gates measured 20.2 / 22.3 / 28.1 min on push runs 35907568200 / 35921139066 / 35902196786). The revised ~74 min is consistent; ~99 min I could not derive — plausibly it counts create-release.yml's gate (~20–25 min), the 4th caller, which this PR deliberately leaves (#715 items 3–4). Realized savings are lower when pushes coalesce: 4 of the last 5 cron-vendor push runs were cancelled while pending.

Non-blocking notes:

  • Every PR's Unit Tests completion now spawns 3 all-jobs-skipped runs (≈0 runner minutes, Actions-tab noise). workflow_run: branches: [main] would cut most of it — as noise reduction only, never as the guard: a fork branch literally named main passes that filter, so the if: checks must stay the gate (the in-file comment already reasons this).
  • Create Release now sits at the third and last workflow_run chain level GitHub permits (Unit Tests → Vendor Update → Create Release); anything ever chained off its completion will silently not fire.
  • cron-conda.yml keeps top-level contents: read where cron-vendor.yml got {}; every job declares its own, so cosmetic only.
  • A cancelled Unit Tests push run means no deploy/update for that push; the next push or Saturday's cron recovers it, and per-run-id concurrency means push runs are no longer coalesced-cancelled.
  • Pure workflow YAML: no Python code path, hence no coverage delta and no test to add (tests/project/test_release_gates.py pins create-release.yml only, untouched).

✅ GOOD TO MERGE @ e0f023d31 — traced all three triggers end-to-end: no main push loses its gate, consumers act only on conclusion=='success' && event=='push' && head_branch=='main', and github.sha-over-head_sha is benign-to-preferable for all three (owner's documented call).

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 24, 2026
@JarryShaw
JarryShaw merged commit bea54df into main Sep 24, 2026
26 checks passed
@JarryShaw
JarryShaw deleted the ci-gate-workflow-run-715 branch September 24, 2026 04:43
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Pull requests that change CI or workflow configuration (ci: subject prefix) perf Pull requests that improve performance (perf: subject prefix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant