Migrate pipeline from CircleCI to GitHub Actions#174
Conversation
|
Status from the automation that opened this pull request — updated as it progresses. AssessmentI checked out a gha-migration branch and inspected rake_github to decide whether it fits our standard Ruby-gem CircleCI-to-GitHub-Actions migration. It does: it's a gem published to RubyGems with the usual ./go/Rakefile build system, git-crypt secrets, and a CircleCI config in our familiar build/test/prerelease/release/merge shape. A few repo-specific details need carrying through, none of them blocking. The test step script calls ChangesI migrated this gem's CI from CircleCI to GitHub Actions. I added two workflows — one for pull requests (lint, test, build, a pre-release publish to RubyGems, and Dependabot auto-merge) and one for the main branch (lint, test, then a gated release) — and removed the old CircleCI config and its helper scripts. The Rakefile now provisions the pipeline through GitHub instead of CircleCI, notifies Slack, and gained small build/release helper tasks; the encrypted CI key moved from the CircleCI folder to the GitHub one, and the gem's dependencies and README were updated to match. The gem needs the libsodium system library, which I kept and wired into the test job (the only job that actually loads it). All checks pass locally — linting, the full unit suite, and a clean-room dependency install — and I provisioned the repository's Actions secret and protected VerificationI independently verified that the CI provisioning for infrablocks/rake_github took effect, using read-only GitHub API calls. The Actions secret ENCRYPTION_PASSPHRASE is present, so CI will have the credential it needs to run. The gated 'release' environment exists and is protected by a required-reviewers rule (the infrablocks Maintainers team must approve releases). Everything the CI pipeline depends on is in place, so this PR is ready for review. Automated checksAutomated checks passed first time. Smoke testI confirmed that PR #174's CI published a working prerelease of the rake_github gem. Querying RubyGems, I found the namespaced prerelease 0.18.0.pre.2.pr174.1.1, published at 2026-07-24T08:47:28Z (after this run's cutoff). I installed exactly that version into a throwaway directory outside the repo using Ruby 3.3.11 (the gem requires Ruby >= 3.3), then loaded rake_github/version and confirmed the RakeGithub::VERSION constant equalled 0.18.0.pre.2.pr174.1.1. The published artifact installs and loads correctly from the outside, so the PR-CI publish path works. Automated reviewI reviewed this CircleCI-to-GitHub-Actions migration for the rake_github gem across correctness, security, safety, standards and code-quality. The change is clean and matches its migration specification in both directions, with no defects that hold up merging. The protective machinery is sound: secrets are guarded against fork and bot pull requests, git-crypt content is checked before anything is uploaded, version-publishing jobs are serialised so releases cannot race, and the dependabot auto-merge only lands if no new commit arrived after the checks passed. One point is worth calling out: two automated reviewers flagged a Ready to mergeThe automated review and checks have passed. This is waiting for a maintainer to merge it. atomic-foundry-status · foundry-run: 2026-07-24T08-28-29-246Z |
phelma
left a comment
There was a problem hiding this comment.
Code Review: #174 - Migrate pipeline from CircleCI to GitHub Actions
Verdict: COMMENT
This is a clean, highly plan-conformant CircleCI → GitHub Actions cutover for
the rake_github gem. Across five lenses (correctness, security, safety,
standards, code quality) no in-scope defects survived verification: the diff
matches the family plan §4 in both directions, protective mechanisms
(git-crypt ciphertext guard, concurrency serialisation, --match-head-commit,
ensure-based version.rb restore) are sound, and the fork/Dependabot secret
guard on the PR prerelease job is correct. It looks safe to merge.
Verified false positive (not counted)
queue: maxis NOT invalid. Three lens findings (2 flagged critical)
claimedqueue: maxunderconcurrency:is not a valid GitHub Actions key.
I verified against the GitHub changelog dated 2026-05-07
(github.blog/changelog/2026-05-07-github-actions-concurrency-groups-now-allow-larger-queues):
queue: maxis a real, GA feature allowing up to 100 queued runs per group,
and only works whencancel-in-progressisfalse— exactly how it is used
here. The agents' (and my own) training predates the feature. No change
required.
Verified correct (not a finding)
- libsodium scoping.
libsodiumis installed only in thetestjob. The
correctness lens verifiedrbnaclis lazilyrequired inside
Provision#encrypt(lib/rake_github/tasks/secrets/provision.rb:72), so
check/build/prerelease/releasenever load it at library-require time
and do not need it. Scoping is correct.
Plan concerns (documented-deliberate; for a human to revisit the plan, not blocking)
These challenge decisions the family plan or PR description documents as
deliberate. They are captured for a plan owner to revisit fleet-wide; none
block this plan-conformant diff.
- 🔵 Security — secrets exposed to same-repo collaborator PR code
(pr.yamlprerelease job). The prerelease job runs PR-branch code with
ENCRYPTION_PASSPHRASEand RubyGems credentials in scope before any review
gate. The fork/Dependabot guard is correct, but any collaborator with push
access could exfiltrate secrets via a PR-branch task change. This is
inherent to the deliberate D8 "prove the publish path pre-merge" design.
Worth revisiting fleet-wide (e.g. anenvironment:approval on the
prerelease publish). - 🔵 Safety — publish-before-push window (
main.yamlprerelease)../go releasepublishes to RubyGems before the bump commit is pushed; a failed
push leaves a published version whose commit never reachedmain. This is
the inherited hazard explicitly listed as out of scope in plan §1. - 🔵 Safety — approval-hold queue accumulation (
main.yamlrelease).
A run awaiting release approval holds themainconcurrency slot, so pushes
during the window queue behind it. Intentional and correct for version-race
safety per plan §4.2; operationally worth monitoring queue depth. - 🔵 Standards — job named
merge-pull-requestvs the reference's
dependabot-auto-merge. Explicitly a Variant A decision per plan §4.1
(plain--merge, not--auto). - 🔵 Standards — no
skip-ci-checkjob inmain.yamlunlike the
reference. Deliberate per D4:GITHUB_TOKENpush semantics + native
[skip ci]handling make a loop-guard job unnecessary. - 🔵 Standards — Slack notify idiom uses
if: ${{ !cancelled() }}+
continue-on-error: truerather than the reference'sif: always(). Plan
§3 marks the §4 YAML as authoritative where it differs from the reference. - 🔵 Code quality —
define_repository_tasksblock mixes token resolution
and the git-crypt guard (~30 lines, hard to unit-test). This is the plan's
verbatim §4.4 prescribed code; extracting helpers would deviate from
"do not restructure anything else in the Rakefile". - 🔵 Code quality — version regex
/(VERSION\s*=\s*')([^']+)(')/only
matches single-quoted literals. Verbatim plan §4.4 code; safe in practice
because rubocop enforces single quotes in this repo. - 🔵 Code quality — gem name /
gem buildduplication across
library:buildandprerelease:publish. Both are plan-prescribed verbatim.
Observation (non-blocking, not counted)
.rubocop.ymladdsdefine_repository_tasksto theMetrics/BlockLength
exclusions. Not in the §4 change list, but a necessary consequence of the
plan-mandated (now longer) RakeGithub block so that the §5library:check
gate passes. Reasonable and in-spirit.
Strengths
- ✅ Attacker-controllable facts (PR title, number, run metadata, HEAD SHA)
are passed viaenv:and referenced as shell$VARS, never${{ }}-
interpolated intorun:— no workflow shell injection. - ✅
GITHUB_TOKENleast privilege: top-levelcontents: read, with
contents: write/pull-requests: writeelevated only where needed. - ✅ Git-crypt ciphertext guard (
\x00GITCRYPTmagic viaFile.binread)
fails closed before uploading garbage asENCRYPTION_PASSPHRASE. - ✅
prerelease:publishrestoresversion.rbandrm_fs the gem in an
ensureblock; namespaced version embeds run/attempt so re-runs never
collide. - ✅ Dependabot merge guarded by
--match-head-commitand[skip ci]subject. - ✅ Requires and dev-dependencies correctly alphabetised; comments are
self-contained and avoid migration/process references. - ✅ Decommission is complete and removes stored credentials without
committing any decrypted secret material.
Review generated by /accelerator:review-pr
Per-Lens Results
Correctness
Summary: Migration largely correct; git-crypt guard, version regex, and
prerelease:publish logic sound; libsodium correctly scoped to test (rbnacl
lazily required). Flagged queue: max as invalid (2× critical) — verified
false positive, it is a real GA feature used correctly.
Security
Summary: Injection and secret-exposure hazards handled correctly; facts
via env, least-privilege tokens, correct fork/Dependabot guard, ciphertext
guard. One residual trust observation: prerelease runs collaborator PR code
with secrets (deliberate D8 design) → plan concern.
Safety
Summary: Unusually careful; ensure-based restore, concurrency
serialisation, --match-head-commit, --ff-only, full timeout coverage. Two
minor observations (publish-before-push window; approval-hold queue) both
documented-deliberate → plan concerns.
Standards
Summary: Tracks the rake_slack reference closely and improves on it
(alphabetised, least-privilege). Deviations (job name, no skip-ci-check,
notify idiom, queue) are all plan-mandated or false positives → plan
concerns.
Code Quality
Summary: Clean, well-commented, fail-fast error messages. Observations
(config block responsibilities, version regex, gem-name/build duplication) all
target plan-prescribed verbatim code → plan concerns.
|
Ready to merge. The automated review and checks have passed; a maintainer can merge this when ready. atomic-foundry-event · foundry-run: 2026-07-24T08-28-29-246Z |
Post-merge prerelease validationValidated the CI side
Consumer side (clean
The |
Cutover to GitHub Actions per the Variant A family plan (gem pilot).
Includes decommission — merging this PR completes the repo's migration.
releaseenvironment gate.github/rake_slack; dependabot auto-merge jobrake_githubsecrets/environments;rake_circle_cidropped.circleci/,scripts/ci/, the CI SSH deploykey pair and its
keys:deploy/deploy_keysprovisioning, and the storedCircleCI/GitHub API credentials (
config/secrets/{circle_ci,github}/)Deliberate decisions (not defects)
This cutover reproduces the CircleCI pipeline's behaviour, warts included;
fixing inherited hazards is post-migration work. In particular:
./go releasepublishes to RubyGems before the version-bump commit ispushed — pre-existing ordering inside the untouched release logic.
mainwith no approval gate; onlyfull releases are gated (
environment: release).merge does not trigger a release build — on CircleCI the merge commit
carried
[skip ci], so this matches. Updates ship with the nexthuman-triggered release.
releasejob pullsmainat approval time, so a delayed approvalpublishes main as it stands then, not the SHA this run tested — parity with
the old
release.sh(which also pulled;prerelease.shdid not, so theprerelease job has no pull).
asdf_install@v1is our own action (infrablocks/github-actions); we arehappy tracking its major version tag.
build system (
./go/rake) and CI stays lean — it just triggers tasks andsupplies secrets/context.
Gemfile.lockcarries transitive major bumps — the unavoidable resolutionof the targeted
bundle lock --update, not scope creep.autocorrects existing code (e.g.
Style/ArgumentsForwarding) — requiredby the
library:checkverification gate, not drive-by refactoring.pipeline:prepare) authenticates with the operator's ambientghlogin (GITHUB_TOKENfallback) instead of a stored PAT — a deliberateparity deviation; the stored token in
config/secrets/github/config.yamlis deleted with the rest of the CircleCI-era credentials.
PR-CI prerelease publish (deliberate, permanent)
pr.yamlhas aprereleasejob that publishes a namespaced pre-release ofthis gem to RubyGems from the PR branch — a permanent CI feature, not
migration-only. This is a deliberate deviation from CircleCI (which published
nothing pre-merge): it proves the publish path before merge instead of
discovering it broken on
main. The version is<committed-version>.pr<PR>.<run>.<attempt>(via the newprerelease:publishRakefile task), so it can never collide with
main'sversion:bump[pre]sequence; the task builds the gem and pushes it straight to RubyGems, then
restores
version.rb, so nothing is committed, tagged, or pushed(
gem releaseis not used — it aborts on the uncommitted version rewrite).The job is skipped for fork
and Dependabot PRs (they hold no secrets), and
merge-pull-requestdoes notdepend on it. PR pre-release versions accumulate permanently on RubyGems —
accepted.
Do not merge manually — the pipeline merges once checks are green.
Disabling the CircleCI project and deleting the
CircleCIdeploy key aredeferred to the end-of-migration sweep.
🏭 This PR was opened by Foundry, Atomic's AI software development
factory. Implementation, review, and fixes are performed by AI agents;
merges happen automatically once the review and checks gates pass.
This task migrates a Ruby gem's CI from CircleCI to GitHub Actions.
migratemigrate-gem2026-07-24T08-28-29-246Zatomic-foundry-pr · foundry-pipeline: migrate · foundry-task: migrate-gem · foundry-run: 2026-07-24T08-28-29-246Z