unsloth/.github/workflows
Daniel Han 2043c734c4
Take two ubuntu jobs off their own runners: absorb one, delete the other (#9360)
Over 400 completed main push runs, 25 of 96 job types execute in under 120s:
1116s of work spread across 25 runners, each queuing for about three hours. Two
of them are dealt with here.

  Security audit :: pytest tests/security                72s exec, 11096s queue
  Unsloth export capability :: capability (ubuntu-latest) 67s exec, 10642s queue

Different problems, so different treatments.

The security suite MOVED, onto the Workflow trigger lint runner
------------------------------------------------------------------------
Same argument that put the lockfile and load-orchestrator lanes into Lint CI in
#9176: work with a narrow trigger, moved into a job that was going to occupy a
runner on this commit anyway, can only reduce the slots a commit takes. Here the
trigger widens too, since this host has no paths filter and security-audit.yml's
pull_request does, so the suite now runs on every pull request rather than on the
ones that touch its paths.

This host and NOT Lint CI, where the other lanes went, and that is the whole
decision. Lint CI installs shellcheck from apt, so its harden-runner has to
permit escalation and an apt mirror; a security gate moved there would run under
a policy weaker than the one it has today. Workflow trigger lint's harden-runner
block is byte-for-byte identical to the one the job carried in security-audit.yml
(block, disable-sudo, the same six endpoints), so nothing about its isolation
changes. harden-runner binds per runner, not per step, which is what makes that
the deciding constraint rather than a detail.

Folded into the existing pytest invocation rather than added as a step of its
own. I wrote it as a separate step first, so a security regression would not be
reported as a workflow-guard failure, and
test_the_guards_run_in_one_pytest_invocation rejected it: one step per module
costs about 15s of interpreter and conftest startup, measured in this repo at
53.9s as one invocation against 300.8s as one each. The guard is right and the
attribution preference is not worth 15s.

pytest and PyYAML are now pinned here to the versions security-audit.yml pinned
them to. That suite runs scripts/lint_workflow_triggers.py as a SUBPROCESS and
asserts on its exit semantics, so a pytest or PyYAML that resolves differently
changes what it is asserting against.

The capability ubuntu leg was DELETED, because it was already duplicated
------------------------------------------------------------------------
That workflow's own comment already explains why it has no macOS leg: every test
in tests/test_export_capability.py goes through _patch(), which monkeypatches
_has_torch, get_device and is_apple_silicon, so a real Mac proves nothing a Linux
runner does not -- and studio-backend-ci.yml runs the same file on ubuntu-latest
as part of `pytest tests/`.

That argument reaches one step further than it was taken. If Backend CI covers
the file on Linux, the ubuntu leg HERE is the duplicate too. Checked: the file is
not in that job's --ignore list. The import-safety test does not need a
torch-free image either; it installs its own builtins.__import__ blocker and
drops preloaded torch/unsloth from sys.modules, so it proves the same thing
inside Backend CI's fully installed environment.

Windows stays. Nothing else in CI runs that file there, and _has_torch's import
probe is the per-OS behaviour the job exists for.

The guard
------------------------------------------------------------------------
tests/studio/test_short_job_absorption.py, wired into the unfiltered job. Both
changes fail silently rather than loudly if they regress, which is what it is
for:

  - the suite still runs somewhere, and no longer runs twice
  - the absorbing job's harden-runner has not widened past the six endpoints the
    suite came with, since "the policy is identical" is the entire justification
    for this host
  - the absorbing job has not gained a paths filter
  - capability still has its Windows leg
  - studio-backend-ci.yml still runs the whole tests/ tree and does not name
    test_export_capability.py, because one line added to that --ignore list
    would remove the coverage the ubuntu leg was deleted for, and nothing would
    turn red

Mutation-tested, each failing exactly one test: drop tests/security from the
invocation; add one endpoint to the allowlist; add
--ignore=tests/test_export_capability.py to Backend CI; remove the Windows leg.

Also corrected a docstring in tests/security/test_scan_packages.py that named
tests-security and what it installs. It was about to become false.

Verification
------------------------------------------------------------------------
tests/security under the host's own -n 4: 409 passed, 6 skipped.
scripts/lint_workflow_triggers.py: OK across 41 workflow files.
All three workflows still parse; security-audit.yml keeps its other 4 jobs.

Net: 7 short ubuntu slots per commit, down to 5.

A note for whoever extends this. The census that found these 15 candidates was
partly stale and I nearly acted on it: the lockfile and load-orchestrator rows
were already absorbed by #9176 and their samples were pre-merge tails, and the
two Local Agent Guides rows show ~0s because they are if-gated to schedule and
dispatch, which is a skip and not a fast job. Read the trigger before ranking by
duration. Of the remaining candidates, Scorecard is blocked by its job-level
id-token: write, the Kaggle gate by a downstream needs:, npm-provenance by an
audit egress policy plus registry.npmjs.org, and the notransport clean-install
lane by a container that asserts several common tools are absent.
2026-08-20 00:05:28 -07:00
..
cache-janitor.yml CI: prune superseded Actions caches (#8167) 2026-08-08 04:20:22 -07:00
ci-capacity.yml Bump the actions group across 1 directory with 11 updates (#8101) 2026-08-07 14:22:15 +02:00
clean-machine-install-ci.yml Let every main run finish instead of cancelling it before it starts (#9082) 2026-08-17 03:59:13 -07:00
consolidated-tests-ci.yml Run the zoo suite in parallel, minus the two files that cannot share a worker (#9285) 2026-08-19 06:25:09 -07:00
cross-platform-parity-ci.yml Stop spending the Actions cache budget on caches nothing reads (#9151) 2026-08-18 04:47:22 -07:00
desktop-app-clean-machine-ci.yml Desktop: ship a complete Linux AppImage (#9113) 2026-08-19 10:09:29 -03:00
interrupted-install-ci.yml Make apt fail fast, so the retry has something to retry (#9260) 2026-08-19 00:19:50 -07:00
kaggle-t4-notebook-ci.yml Give Unsloth Studio its first CUDA coverage, on a Kaggle T4 (#8489) 2026-08-14 06:30:18 -07:00
kaggle-t4-studio-gpu-ci.yml Give Unsloth Studio its first CUDA coverage, on a Kaggle T4 (#8489) 2026-08-14 06:30:18 -07:00
lint-ci.yml Make apt fail fast, so the retry has something to retry (#9260) 2026-08-19 00:19:50 -07:00
local-agent-guides-ci.yml Support OpenCode V2 in unsloth start (#9275) 2026-08-20 04:42:41 +02:00
lockfile-audit.yml Run two short jobs as background lanes of Lint CI, not on their own runners (#9176) 2026-08-18 05:27:55 -07:00
mlx-ci.yml Move the two version-incidental macOS jobs onto the emptier pool (#9277) 2026-08-19 03:21:25 -07:00
model-catalog-network-check.yml Reject an incompatible GGUF and base pair before the download, not after (#8158) 2026-08-09 04:37:02 -07:00
notebooks-ci.yml Give the Colab freeze a pip cache, like the job beside it already has (#9276) 2026-08-19 03:22:16 -07:00
ossf.yml Let every main run finish instead of cancelling it before it starts (#9082) 2026-08-17 03:59:13 -07:00
pester-guard-ci.yml Bump the actions group across 1 directory with 11 updates (#8101) 2026-08-07 14:22:15 +02:00
publish-desktop-updater.yml Make publish-desktop-updater.yml manual-dispatch only (#8356) 2026-08-10 20:47:31 -07:00
release-desktop.yml Desktop: ship a complete Linux AppImage (#9113) 2026-08-19 10:09:29 -03:00
security-audit.yml Take two ubuntu jobs off their own runners: absorb one, delete the other (#9360) 2026-08-20 00:05:28 -07:00
stale.yml Bump the actions group across 1 directory with 11 updates (#8101) 2026-08-07 14:22:15 +02:00
startup-profile-ci.yml Make the startup profile a gate, with budgets from its own measurements (#8965) 2026-08-16 02:37:32 -07:00
studio-api-smoke.yml Make apt fail fast, so the retry has something to retry (#9260) 2026-08-19 00:19:50 -07:00
studio-backend-ci.yml Stop spending the Actions cache budget on caches nothing reads (#9151) 2026-08-18 04:47:22 -07:00
studio-export-capability-ci.yml Take two ubuntu jobs off their own runners: absorb one, delete the other (#9360) 2026-08-20 00:05:28 -07:00
studio-frontend-ci.yml Drain the sampling debounce long enough for the node CI actually runs (#9332) 2026-08-19 23:55:01 -07:00
studio-inference-smoke.yml Make apt fail fast, so the retry has something to retry (#9260) 2026-08-19 00:19:50 -07:00
studio-load-orchestrator-ci.yml Run two short jobs as background lanes of Lint CI, not on their own runners (#9176) 2026-08-18 05:27:55 -07:00
studio-mac-install-matrix.yml Stop running every macOS workflow on every commit to main (#9174) 2026-08-18 04:22:15 -07:00
studio-mac-ui-smoke.yml Print what the server said when an inference smoke request 4xx's (#9202) 2026-08-18 21:02:07 -07:00
studio-tauri-smoke.yml Move the two version-incidental macOS jobs onto the emptier pool (#9277) 2026-08-19 03:21:25 -07:00
studio-ui-smoke.yml Cache the .deb set webkit needs, so a dead mirror costs a miss not a shard (#9289) 2026-08-19 06:57:19 -07:00
studio-update-smoke.yml Make apt fail fast, so the retry has something to retry (#9260) 2026-08-19 00:19:50 -07:00
studio-windows-api-smoke.yml Stop spending the Actions cache budget on caches nothing reads (#9151) 2026-08-18 04:47:22 -07:00
studio-windows-inference-smoke.yml Print what the server said when an inference smoke request 4xx's (#9202) 2026-08-18 21:02:07 -07:00
studio-windows-ui-smoke.yml Stop spending the Actions cache budget on caches nothing reads (#9151) 2026-08-18 04:47:22 -07:00
studio-windows-update-smoke.yml Time the install from CI, without changing the installers (#9153) 2026-08-18 04:23:09 -07:00
version-compat-ci.yml Stop spending the Actions cache budget on caches nothing reads (#9151) 2026-08-18 04:47:22 -07:00
wheel-smoke.yml Let every main run finish instead of cancelling it before it starts (#9082) 2026-08-17 03:59:13 -07:00
windows-application-control-ci.yml Let every main run finish instead of cancelling it before it starts (#9082) 2026-08-17 03:59:13 -07:00
workflow-trigger-lint.yml Take two ubuntu jobs off their own runners: absorb one, delete the other (#9360) 2026-08-20 00:05:28 -07:00