Runners: a bare runs-on: self-hosted is x86_64 work only

c44c033 · Gabriel Morell · 2026-10-25 01:26

7 files +43 -12
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud/workflows.ex
+23 −9
@@ -1527,10 +1527,7 @@ defmodule GitGud.Workflows do
1527 1527 |> where([j], j.id == ^job_id)
1528 1528 |> where([j], j.status == "queued")
1529 1529 |> approved_only()
1530 − |> where(
1531 − [j],
1532 − fragment("(? -> 'labels') <@ to_jsonb(?::text[])", j.runs_on, ^match_labels)
1533 − )
1530 + |> runs_on_matches(runner, match_labels)
1534 1531 |> under_max_parallel()
1535 1532 |> lock("FOR UPDATE SKIP LOCKED")
1536 1533 |> Repo.one()
@@ -1572,10 +1569,7 @@ defmodule GitGud.Workflows do
1572 1569 scoped_job_query(runner)
1573 1570 |> where([j], j.status == "queued")
1574 1571 |> approved_only()
1575 − |> where(
1576 − [j],
1577 − fragment("(? -> 'labels') <@ to_jsonb(?::text[])", j.runs_on, ^match_labels)
1578 − )
1572 + |> runs_on_matches(runner, match_labels)
1579 1573 |> where([j], j.id not in ^passed_over)
1580 1574 |> under_max_parallel()
1581 1575 |> order_by([j], asc: j.id)
@@ -1685,7 +1679,8 @@ defmodule GitGud.Workflows do
1685 1679 # `self-hosted` is implied for an x86_64 runner only: the workflows
1686 1680 # that say `runs-on: self-hosted` were written when every runner was
1687 1681 # x86_64, and some build images that deploy to x86_64 machines. Any
1688 − # other runner takes `self-hosted` work only if its labels list it.
1682 + # other runner needs `self-hosted` in its labels for `[self-hosted,
1683 + # ...]` work, and never takes a bare `self-hosted` (`runs_on_matches/3`).
1689 1684 #
1690 1685 # Built into jsonb on the SQL side from a text array rather than
1691 1686 # interpolated as a JSON string: a string parameter bound to a
@@ -1697,6 +1692,25 @@ defmodule GitGud.Workflows do
1697 1692 Enum.uniq(implied ++ labels)
1698 1693 end
1699 1694
1695 + # A bare `runs-on: self-hosted` is x86_64 work, whatever labels another
1696 + # runner carries: labels are fixed at registration, and an arm runner
1697 + # needs `self-hosted` anyway for the `[self-hosted, runner-aarch64]`
1698 + # legs written for it.
1699 + defp runs_on_matches(query, %Runner{architecture: arch}, match_labels) do
1700 + query =
1701 + where(
1702 + query,
1703 + [j],
1704 + fragment("(? -> 'labels') <@ to_jsonb(?::text[])", j.runs_on, ^match_labels)
1705 + )
1706 +
1707 + if arch == "x86_64" do
1708 + query
1709 + else
1710 + where(query, [j], fragment("(? -> 'labels') <> '[\"self-hosted\"]'::jsonb", j.runs_on))
1711 + end
1712 + end
1713 +
1700 1714 # A runner only claims jobs in its scope:
1701 1715 # * repo-scoped runner → jobs in that repo
1702 1716 # * org-scoped runner → jobs in any repo owned by that org
modified test/git_gud/workflows/matrix_dispatch_test.exs
+20 −3
@@ -329,11 +329,28 @@ defmodule GitGud.Workflows.MatrixDispatchTest do
329 329 assert %WorkflowJob{job_id: "arm"} = Workflows.claim_next_job(arm)
330 330 end
331 331
332 − test "an aarch64 runner labelled self-hosted takes self-hosted work", %{repo: repo} do
332 + test "an aarch64 runner labelled self-hosted still leaves bare self-hosted work", %{
333 + repo: repo
334 + } do
333 335 self_hosted_job(repo)
334 336
335 − arm = aarch64_runner(repo, ["self-hosted", "ubuntu-24.04-arm"])
336 − assert %WorkflowJob{job_id: "anywhere"} = Workflows.claim_next_job(arm)
337 + arm = aarch64_runner(repo, ["self-hosted", "runner-aarch64"])
338 + assert Workflows.claim_next_job(arm) == nil
339 +
340 + run_workflow(repo, """
341 + name: arm
342 + on: push
343 + jobs:
344 + arm:
345 + runs-on: [self-hosted, runner-aarch64]
346 + steps:
347 + - run: true
348 + """)
349 +
350 + assert %WorkflowJob{job_id: "arm"} = Workflows.claim_next_job(arm)
351 +
352 + assert %WorkflowJob{job_id: "anywhere"} =
353 + Workflows.claim_next_job(runner(repo, ["ubuntu-latest"]))
337 354 end
338 355
339 356 test "the architecture is a label", %{repo: repo} do

Parents: a119a0e