ci: remove the ruby dependency from the org reusable workflows#153
Merged
Conversation
Codex Rails failed on ARC in 7s with `ruby: command not found`. The Hetzner runner image ships Ruby 3.1.2; the ARC image ships none. Both workflows used Ruby only as a YAML/JSON parser and as the host for the review-thread guard. Restores codex-rails-check.yml and review-thread-guard.yml into this repo (removed at 0783dc1 when the engine moved to .github-private) with Ruby replaced by python3 + PyYAML, and ports check-pr-review-threads.rb to check_pr_review_threads.py using the standard library only. PyYAML is present on both runner images, verified by probing them rather than by reading a Dockerfile. Because "the image has the library" is exactly the assumption that silently broke the deploy cost gate, both workflows now assert their toolchain up front and self-test against fixtures that must fail. A guard that stops verifying goes red instead of green. Also drops the dead `blacksmith-4vcpu-ubuntu-2404` runner default, which would queue forever rather than fail, and makes the Ruby test step fail loudly instead of skipping when a repo has a Ruby suite and the runner has no ruby. The Python port matches the Ruby original on 190 differential cases across 19 payload fixtures, 5 severities and both --include-outdated settings. Consumers pin this repo by SHA and must bump both `uses:` and `guard_ref`.
The header comments I added in 5721334 listed the organisation's private consumer repositories by name, and named a specific private repo's protected branch, inside a repository that is public. That is the same disclosure the automation move at 0783dc1 was meant to avoid. Reworded to describe the constraint without the inventory. No behaviour change; comments only. Landed as a new commit rather than an amend, since 5721334 is already pushed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Both reusable workflows in this repo used Ruby — as a YAML/JSON parser in
codex-rails-check, and as the host for the review-thread guard script. Our self-hosted runner images ship Ruby 3.1.2. The Actions Runner Controller (ARC) image we are migrating to ships none, so the check dies in 7 seconds:Both images were probed directly rather than read out of a Dockerfile — a job running
command -vfor 95 binaries on one runner of each type. Ruby is the only tool either workflow depends on that the two images disagree about:ruby/gempython3python3 -c 'import yaml'(PyYAML)nodenode -e "require('yaml')"/js-yamlyqgh,git,jq,curl,tar,make,gcc,perl,opensslThe review-thread guard is the more serious of the two. It produces a status check that is required on a protected branch, and its only real step is
ruby .../check-pr-review-threads.rb. It has not broken yet only because the calling repository's runner variable still resolves to the old fleet — the ARC fallback written into the caller is dead code. Repointing that variable, which is the whole direction of travel, would have failed the check on every PR and stopped all merges at once.What changed
Both workflows are restored into this repo — they were removed at
0783dc1when the org automation engine moved to a private repository — with Ruby replaced.They have to live here rather than in the private repo, and the reason is checkable:
No repository outside it can call its reusable workflows. Every consumer still pins
evalops/.github@<sha>, and those pins keep resolving only because the SHAs predate0783dc1. For anyone to pin a fixed version, the shared workflows have to exist at this repo's HEAD.Nothing newly private is published:
check-pr-review-threads.rbhas been in this repo's public history sinced45c0bca, and the restored files carry no scripts beyond the guard itself.Replacement:
python3+ PyYAML, with the assumption made loudPyYAML is present on both images today, verified above. "The image already has the library" is exactly the assumption that has silently broken a gate here before — a required check whose YAML-structural predicates quietly degraded to line matching for months because the parser was never installed and nothing asserted it. So the dependency is asserted rather than assumed:
codex-rails-check.ymlopens with a preflight that exits 1 with a named::error::ifpython3orimport yamlis unavailable, then self-tests the validator against a known-bad YAML file and fails if the bad file is accepted.review-thread-guard.ymlpreflightspython3,ghand the guard script's existence, then runs the guard against two committed fixtures: a clean PR that must exit 0, and a PR with an unresolved high-severity thread that must exit 1. If the guard ever starts passing vacuously, the check goes red instead of green.Rejected alternatives:
yq(absent on both images; adds a download and a checksum to maintain), a vendored JS parser (hand-rolling YAML is how a validator ends up accepting invalid input), andactions/setup-*(a per-run install for a library both images already carry).check-pr-review-threads.rb→check_pr_review_threads.pyStandard library only —
json,re,subprocess,argparse. Same severity ranking, same informational-summary suppression, same GraphQL documents and pagination, same annotations, same exit codes.One deliberate behaviour change: if
ghis missing, the port raises instead of returning an empty feedback list. A guard reporting "no unresolved feedback" because it could not query anything is the failure mode this PR exists to prevent.Two other ARC hazards fixed in passing
codex-rails-check.yml'srunner_labeldefaulted to a Blacksmith label. Blacksmith was removed org-wide; a job targeting a label with no runners queues forever rather than failing — no red X, no annotation, no notification. The default is nowubuntu-latest.::error::when a repo hastest/**/*_test.rband the runner has noruby, instead of skipping. No current consumer ships a Ruby suite, so the path is inert today.Verification
Every number below comes from a command that was run, not from reading the diff.
--include-outdatedsettings — byte-identical stdout, stderr and exit code, plus matching exit 2 on an invalid--min-severity.codex-rails-check: every step extracted from the YAML and executed against synthetic repositories, covering each step's pass and fail path, plus a differential against the Ruby original across 11 repo variants × 5 steps. The Python version fails on exactly the inputs the Ruby version failed on.review-thread guard self-test failed: a blocking fixture was reported clean. This gate is not verifying anything.Removingpython3,gh, or the script itself each fails the preflight with the missing tool named.actionlint(which also runs shellcheck over everyrun:block) is clean on both workflows.Both workflows were then exercised end-to-end on real ARC runners from the consumer-side PR, including a deliberate breakage that turned
codex-rails-checkred with a precise parse error and a revert that turned it green again.Which version this restores
codex-rails-check.ymlhere is theec91f80bstep set, which is what most consumers call. Two consumers pin the later7c84584, which adds six steps. Those are not carried forward, deliberately:0783dc1and now live only in the private automation repo. Restoring steps that invoke deleted scripts would ship something broken by construction.go run github.com/rhysd/actionlint/..., andgois absent on the ARC image — porting it as-is would trade a Ruby break for a Go break.Those two consumers should stay on
7c84584until someone decides whether those org-only steps belong in the shared workflow at all.Follow-up required — this PR alone changes almost nothing
Consumers pin by SHA, so until the pins move they keep running the Ruby version. Two things worth knowing before merging:
@main, andmainhas had nocodex-rails-check.ymlsince0783dc1. Its check has not run since May — the workflow reference is unresolvable, so there is nothing to fail. That repo needs no pin bump; merging this restores its check.review-thread-guardcallers passguard_refas well as theuses:SHA. Both must move to this merge commit, or the job checks out a tree with nocheck_pr_review_threads.pyand fails its own preflight.Consumer pin inventory and the bump PR: evalops/maestro-internal#3168.
agent-authorship-label.ymlalso runs Ruby and is still pinned by three consumers. Not fixed here: it is a separate reusable workflow with its own helper script, and it is not currently routed to ARC.