Migrate pipeline from CircleCI to GitHub Actions - #169
Conversation
phelma
left a comment
There was a problem hiding this comment.
Code Review: #169 - Migrate pipeline from CircleCI to GitHub Actions
Verdict: COMMENT
This is a clean, plan-conformant CircleCI→GitHub Actions cutover. The five lenses (correctness, security, safety, standards, code quality) found no in-scope critical or major defects: PR-controlled inputs are consistently passed via env rather than interpolated into shell, the secret-bearing prerelease job is correctly gated to same-repo non-Dependabot PRs on pull_request (not pull_request_target), the Rakefile provisioning adds robust git-crypt-ciphertext guards, and the prerelease:publish task restores version.rb in an ensure block. An independent check of the diff against family-plan §4 found every required change present, nothing beyond the plan changed (the sole unlisted change — a .rubocop.yml BlockLength exclusion for define_repository_tasks — is a legitimate consequence of the enlarged RakeGithub block, needed for library:check to pass), and no CircleCI residue.
Cross-Cutting Themes
queue: maxconcurrency key (flagged by: correctness, safety) — Flagged as an invalid Actions schema key. This is a plan concern, not a defect: family plan §4.2 mandatesqueue: maxverbatim and cites its GA (2026-05-07 changelog). The diff faithfully reproduces the authoritative plan YAML, so it is plan-conformant. Recorded for a human to revisit the plan if the feature name/behaviour differs from what was assumed.
Strengths
- ✅ Untrusted PR-controlled values (title, number, head SHA) passed via env, never interpolated into
run:— closes the GHA script-injection vector. - ✅ Prerelease job double-gated (
head.repo.full_name == github.repositoryANDuser.login != 'dependabot[bot]'); dependabot auto-merge gates on the immutableuser.login, not the spoofablegithub.actor. - ✅
prerelease:publishrestoresversion.rband removes the built gem in anensureblock; the passphrase guard rejects git-crypt ciphertext before upload; token resolution fails fast with actionable messages. - ✅ Every job carries an explicit
timeout-minutes; release usesgit pull --ff-onlyto fail loudly on non-fast-forward. - ✅ Requires and dev-dependencies remain alphabetical; release job correctly omits
documentation:updateto match the repo's originalrelease.sh.
General Findings
- 🔵 Security:
checkout@v4in the prerelease job persists the workflow token by default;with: persist-credentials: falseremoves a residual token from the runner (suggestion, inline). - 🔵 Security: Slack notify steps interpolate
job.statusinto therun:string; not attacker-controllable today, but passing it via env would keep the codebase uniformly free of context-into-run:interpolation (suggestion). - 🔵 Code Quality: the prerelease
version_patternregex is coupled to the single-quotedVERSION = '...'style and unanchored (suggestion, inline). - 🔵 Standards: notify guards use
!cancelled()where therake_slackreference usesalways(); internally consistent and arguably an improvement — flagged only as conscious divergence (suggestion).
Plan Concerns (documented-deliberate — do not block)
- 🟡
queue: max(correctness/safety) — plan §4.2 mandates it and cites GA. - 🔵 "while this pipeline beds in" comment (standards) — plan §4.4 step 4 explicitly instructs keeping this exact comment; flagging it contradicts the plan.
- 🔵 Triplicated unlock/rubygems provisioning steps (code-quality) — the flat per-job scaffolding is a deliberate §1 decision (logic lives in
./go/rake, CI stays lean).
Noted Non-Issue (false positive)
- The prerelease version can stack a second prerelease marker (
0.8.0.pre.2.pr169.5.1) when the committed base is itself.pre.N. This is a validGem::Versionand cannot collide withmain's sequence — no defect.
Review generated by /accelerator:review-pr
| group: pr-prerelease-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: false | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔵 Security (suggestion)
The prerelease job runs PR-branch code after actions/checkout@v4, which by default persists the workflow GITHUB_TOKEN in .git/config on the runner. The top-level permission is contents: read so the residual token is low-value, but arbitrary same-repo PR-branch code executes in the same job that also holds ENCRYPTION_PASSPHRASE and the RubyGems credential.
Suggestion: add with: persist-credentials: false to this checkout — the job publishes via the RubyGems credential, not the checkout token.
| end | ||
|
|
||
| version_file = 'lib/rake_process_manager/version.rb' | ||
| version_pattern = /(VERSION\s*=\s*')([^']+)(')/ |
There was a problem hiding this comment.
🔵 Code Quality (suggestion)
version_pattern = /(VERSION\s*=\s*')([^']+)(')/ matches only a single-quoted VERSION assignment and is unanchored. If version.rb is reformatted (double quotes) or a VERSION-prefixed token appears earlier, the match silently fails (caught by unless base) or captures the wrong token (not caught).
Suggestion: anchor and accept either quote style, e.g. /^(\s*VERSION\s*=\s*['"])([^'"]+)(['"])/, or add a brief comment noting the pattern assumes the current single-quoted format.
Part of PP-709.
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-22T17-48-59-192Zatomic-foundry-pr · foundry-pipeline: migrate · foundry-task: migrate-gem · foundry-run: 2026-07-22T17-48-59-192Z