Skip to content

ci: run our own code on our runners, forks stay on GitHub's - #157

Merged
askalf merged 11 commits into
masterfrom
ci/own-code-on-our-runners
Oct 7, 2026
Merged

askalf merged 11 commits into
masterfrom
ci/own-code-on-our-runners

Conversation

@askalf

@askalf askalf commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

Every required check here ran on GitHub's hosted runners, so each hosted outage held every PR. This follows askalf/dario#1531: our own code moves to browser-bridge-exec, the non-root one-job runners on our host. Fork PRs stay on GitHub's.

A single runs-on expression picks the runner per job. These resolve to ubuntu-latest:

  • A fork's PR and a Dependabot PR, which run code nobody here wrote.
  • Every run in a fork repository, which has no browser-bridge-exec runners.

Everything else (pushes, same-repo PRs, schedules, dispatches) runs on our runners.

Moved (own-code expression):

  • build.yml unit-tests
  • actionlint
  • truecopy-gate.yml verify pinned tool surface
  • fleet-status-self-test
  • hygiene
  • labels.yml manifest matches the repo
  • fieldpass-ci.yml fieldpass-test and fieldpass-test-node20

Moved (this repository only, never runs PR code): these use browser-bridge-exec in askalf/browser-bridge and GitHub's runners in a fork repository.

  • fleet-status.yml, which posts fleet/verify and fleet/review and runs only the default branch's script.
  • fleet-status-backfill.yml, which runs only on dispatch.

.github/actionlint.yaml declares the browser-bridge-exec label, since actionlint knows only GitHub's runner labels.

docker-build (required). The exec runners have no Docker daemon, because Docker on our host is root. The job is split three ways:

  • docker-build-inputs (our runner) checks which files changed. It lists paths with git diff --no-renames -z, so moving a root *.mjs module into a directory, out of the Dockerfile's COPY *.mjs, counts as a change. A path with a non-ASCII byte is matched whole.
  • docker-build-image (GitHub's runners) is the original build and boot smoke. It runs on every push to master. On a PR it runs only when a file the image is built from changed: the Dockerfile, .dockerignore, package.json/package-lock.json, the root *.mjs files the Dockerfile copies, or build.yml.
  • docker-build, the required name, runs on our runner and passes only when the image job passed or was not owed.

Left on GitHub's runners:

  • codeql.yml analyze (javascript-typescript): CodeQL sizes itself to the machine, and every repo's exec runners share one host, so concurrent analyses would exhaust its memory.
  • stealth.yml: builds and runs the image (Docker), and is not a required check.
  • fuzz.yml: not required, and CPU-heavy on a shared host. Same call as dario#1531.
  • cflite.yml and scorecard.yml: Docker-based actions.
  • release.yml: multi-arch Docker build and push.
  • fieldpass-publish.yml: npm trusted publishing (OIDC) needs a GitHub-hosted runner.
  • pr-triage.yml (pull_request_target) and fleet-review-relay.yml (pull_request_review): they handle outside input. The relay only matters for fork PRs.

Tests

  • scripts/fleet-status.test.mjs evaluates each runs-on expression with the evaluator that file already has. 216 pass locally.

    • Own-code expression: a fork's PR, a fork's review and a Dependabot PR resolve to ubuntu-latest, as does every event in a fork repository. A same-repo PR, a push, a schedule and a dispatch resolve to browser-bridge-exec.
    • Repository-only expression: every event here resolves to ours, and a fork repository's resolve to GitHub's. Only fleet-status.yml and fleet-status-backfill.yml use it.
    • codeql.yml stays on ubuntu-latest.
  • test/build-inputs.test.mjs (new) runs the docker-build-inputs step's own script against scratch repositories. Each of these owes the image build:

    • a root module change
    • each image input
    • moving a root module into a directory
    • moving a module into the root
    • a non-ASCII root module name
    • any push

    Modules in directories, tests and docs owe nothing. 7 pass locally. With --no-renames removed from the step, the move test fails.

  • scripts/check-hygiene.mjs --range passes on the commits.

  • This PR's own checks run on browser-bridge-exec, since a same-repo PR uses its branch's workflow files. They are the live proof.

GitHub's hosted queue has gone down several times this quarter, and
every required check here ran on ubuntu-latest, so each outage held
every PR. Pushes, same-repo PRs, schedules and dispatches now run on
browser-bridge-exec, the non-root one-job runners on our host. A fork's
PR and a Dependabot PR run code nobody here wrote and stay on GitHub's
runners, chosen per job by one runs-on expression.

docker-build needs a Docker daemon, which browser-bridge-exec does not
have. The image build and boot smoke run on GitHub's runners on every
push to master and on a PR that changes what the image is built from
(the Dockerfile, .dockerignore, the package files, the root .mjs files
it copies, or build.yml); the required check runs here and passes only
when that job passed or was not owed.

fleet-status posts fleet/verify and fleet/review and runs only the
default branch's script, so it moves for every event, as does the
dispatch-only backfill. CodeQL gets 2G and two threads on our runner.
scripts/fleet-status.test.mjs evaluates the expression in each
workflow that uses it.
@github-actions github-actions Bot added github_actions Pull requests that update GitHub Actions code tests Test suite and CI size/M 50-199 hand-written lines labels Oct 6, 2026
actionlint knows only GitHub's runner labels and failed the two
workflows that name browser-bridge-exec literally.
@askalf
askalf marked this pull request as ready for review October 6, 2026 17:26

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: request changes. The runner selection distinguishes fork and Dependabot PRs from same-repository events, and the Docker filter covers the current Dockerfile inputs. Remove the private deployment location from the new public comments before merging.

1. Blocking: .github/actionlint.yaml:3

# non-root ephemeral exec runners on our host (platform deploy/gha-runners, browser-bridge-exec-e1)

This comment publishes the internal runner deployment path and a specific runner instance name. The deployment path is also repeated in the new runner-selection comments across the workflows; neither detail is needed to declare the public runner label.

Suggested fix:

Remove the internal deployment path and instance name here and remove the deployment path from the repeated workflow comments. Keep the runner label and the explanation of which events use it.

rule:secret-exposure

askalf added 3 commits October 6, 2026 17:41
…hosted

docker-build-inputs listed changed paths with git diff --name-only, which
reports only the destination of a rename. Moving a root module such as
session-broker.mjs into a directory, out of the image's COPY *.mjs,
read as no image input, so the image job was skipped and docker-build
passed. The filter now uses --no-renames and -z, so a move lists both
paths whole, and test/build-inputs.test.mjs runs the step's own script
against scratch repositories, including that move.

In a fork of this repository a push, schedule or dispatch carries no
pull_request, so the runs-on expression chose browser-bridge-exec,
which a fork does not have. The own-code expression now also sends any
run outside askalf/browser-bridge to GitHub's runners, and fleet-status
and its backfill use browser-bridge-exec only here.
@github-actions github-actions Bot added size/L 200-799 hand-written lines and removed size/M 50-199 hand-written lines labels Oct 6, 2026
CodeQL sizes itself to the machine, and the exec runners of every repo
share one host, so concurrent analyses from several repos exhausted its
memory. The analyze job runs on ubuntu-latest again, without the
ram/threads caps it carried for our runner. scripts/fleet-status.test.mjs
checks that codeql.yml stays on GitHub's runners.
Comment thread scripts/fleet-status.test.mjs Fixed

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve. The runner expressions keep fork and Dependabot PR jobs on hosted runners while moving trusted jobs to the repository's runners. The Docker input filter matches the Dockerfile's sources, covers moves and non-ASCII paths in tests, and keeps the required check dependent on a successful image build when one is needed.

Docker reads a Dockerfile-specific ignore file, Dockerfile.dockerignore,
in preference to .dockerignore. A PR that added one could drop the
image's entry point while the input check said nothing changed, so the
image smoke was skipped and the required docker-build passed.

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve. The runner expressions keep fork and Dependabot PR jobs on hosted runners, while the status jobs continue to check out the default branch. The image-input filter covers the Dockerfile's copied files and rename cases, and the required Docker check propagates input-check and image-build failures.

Minor:

  • scripts/fleet-status.test.mjs:21: This helper has no callers. The new runner tests inspect runs-on expressions directly, so the trigger-block parser adds unused code rather than test coverage.

Nothing calls it since the runner checks stopped reading triggers;
CodeQL flagged it as an unused function.

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve. The runner expressions keep fork and Dependabot PR jobs hosted while routing same-repository work to the exec runners. The Docker input filter covers the Dockerfile's current inputs, including renames and its specific ignore file, with scratch-repository tests; the required check still fails if input detection or an owed image build fails.

@askalf
askalf merged commit cc01ad1 into master Oct 7, 2026
22 checks passed
@askalf
askalf deleted the ci/own-code-on-our-runners branch October 7, 2026 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code size/L 200-799 hand-written lines tests Test suite and CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants