diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0edafc5aa960..70ca13c2a634 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -494,15 +494,22 @@ jobs: if changes["result"] != "success": print(f"changes: {changes['result']}", file=sys.stderr) sys.exit(1) - preflight = needs["linux-preflight"] - if preflight["result"] != "success": - print(f"linux preflight did not pass: {preflight['result']}", file=sys.stderr) - sys.exit(1) outputs = changes.get("outputs", {}) macos_route = outputs.get("macos") if macos_route not in {"true", "false"}: print(f"invalid route macos={macos_route!r}", file=sys.stderr) sys.exit(1) + preflight = needs["linux-preflight"] + if macos_route == "true": + if preflight["result"] != "success": + print(f"linux preflight did not pass: {preflight['result']}", file=sys.stderr) + sys.exit(1) + elif preflight["result"] not in {"success", "skipped"}: + print( + f"linux preflight had unexpected result: {preflight['result']}", + file=sys.stderr, + ) + sys.exit(1) macos_result = needs["macos"]["result"] if macos_route == "true": if macos_result != "success": @@ -530,7 +537,11 @@ jobs: - guards - ghosttykit-release-check - web - if: ${{ always() }} + # This job exists only to admit macOS work after the cheap layer passes. + # This replaces the old unconditional `if: ${{ always() }}`: if the change + # router explicitly says macOS is irrelevant, skip the runner allocation. + # Missing/invalid route output fails open by running the preflight. + if: ${{ always() && needs.changes.outputs.macos != 'false' }} runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }} timeout-minutes: 5 steps: diff --git a/tests/test_ci_linux_guard_routing.py b/tests/test_ci_linux_guard_routing.py index 823515a2b1d0..55680ef191ab 100644 --- a/tests/test_ci_linux_guard_routing.py +++ b/tests/test_ci_linux_guard_routing.py @@ -9,7 +9,13 @@ from pathlib import Path from test_ci_change_areas import ( - linux_preflight_needs, run_guard_status, run_linux_preflight, workflow_job_step_script, + linux_preflight_needs, + run_guard_status, + run_linux_preflight, + run_tests_gate, + tests_gate_needs, + workflow_job_block, + workflow_job_step_script, ) @@ -62,6 +68,24 @@ def test_candidate_router_cannot_disable_its_own_guards(self): self.assertEqual(dict(line.split("=", 1) for line in output.read_text().splitlines()), dict.fromkeys(JOBS, "true")) + def test_linux_preflight_skips_when_macos_route_is_false(self): + block = workflow_job_block("linux-preflight") + self.assertIn( + "if: ${{ always() && needs.changes.outputs.macos != 'false' }}", + block, + ) + + no_macos = tests_gate_needs(macos="false", macos_result="skipped") + no_macos["linux-preflight"]["result"] = "skipped" + result = run_tests_gate(no_macos) + self.assertEqual(result.returncode, 0, result.stderr) + + macos = tests_gate_needs() + macos["linux-preflight"]["result"] = "skipped" + result = run_tests_gate(macos) + self.assertNotEqual(result.returncode, 0) + self.assertIn("linux preflight did not pass: skipped", result.stderr) + def test_docs_skip_all_five_guards_and_gate_succeeds(self): for path in ("CLAUDE.md", "AGENTS.md", "Packages/macOS/AGENTS.md", "README.md", "README.ja.md", "docs/build.md", "plans/cache.md"):