Skip to content

Migrate pipeline from CircleCI to GitHub Actions#226

Closed
phelma wants to merge 1 commit into
mainfrom
gha-migration
Closed

Migrate pipeline from CircleCI to GitHub Actions#226
phelma wants to merge 1 commit into
mainfrom
gha-migration

Conversation

@phelma

@phelma phelma commented Jul 14, 2026

Copy link
Copy Markdown

Cutover to GitHub Actions per the Variant A family plan (gem pilot).
Includes decommission — merging this PR completes the repo's migration.

  • main + pr workflows: loop-guard, check/test, prerelease, release environment gate
  • git-crypt unlock on the runner; encrypted CI GPG key moved to .github/
  • Slack notifications via rake_slack; dependabot auto-merge job
  • Rakefile provisioning swapped to rake_github secrets/environments; rake_circle_ci dropped
  • CircleCI pipeline removed: .circleci/, scripts/ci/, the CI SSH deploy
    key pair and its keys:deploy/deploy_keys provisioning, and the stored
    CircleCI/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 release publishes to RubyGems before the version-bump commit is
    pushed — pre-existing ordering inside the untouched release logic.
  • Prerelease publishes on every push to main with no approval gate; only
    full releases are gated (environment: release).
  • Dependabot auto-merge accepts any update type that passes checks, and the
    merge does not trigger a release build — on CircleCI the merge commit
    carried [skip ci], so this matches. Updates ship with the next
    human-triggered release.
  • The release job pulls main at approval time, so a delayed approval
    publishes main as it stands then, not the SHA this run tested — parity with
    the old release.sh (which also pulled; prerelease.sh did not, so the
    prerelease job has no pull).
  • asdf_install@v1 is our own action (infrablocks/github-actions); we are
    happy tracking its major version tag.
  • Job scaffolding is repeated flat per job by design: the logic lives in the
    build system (./go/rake) and CI stays lean — it just triggers tasks and
    supplies secrets/context.
  • Gemfile.lock carries transitive major bumps — the unavoidable resolution
    of the targeted bundle lock --update, not scope creep.
  • Small library hunks may appear where the refreshed toolchain's rubocop
    autocorrects existing code (e.g. Style/ArgumentsForwarding) — required
    by the library:check verification gate, not drive-by refactoring.
  • Provisioning (pipeline:prepare) authenticates with the operator's ambient
    gh login (GITHUB_TOKEN fallback) instead of a stored PAT — a deliberate
    parity deviation; the stored token in config/secrets/github/config.yaml
    is deleted with the rest of the CircleCI-era credentials.

Do not merge manually — the pipeline merges once checks are green.
Disabling the CircleCI project and deleting the CircleCI deploy key are
deferred to the end-of-migration sweep.

@phelma phelma left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Code Review: #226 - Migrate pipeline from CircleCI to GitHub Actions

Verdict: COMMENT

This is a faithful, tightly plan-conformant Variant A cutover: the workflow YAML, Rakefile edits, gemspec/lockfile swap, GPG-key move, README retitling and CircleCI decommission all match the family plan, and the repo-specific parameterisations (version:bump[minor] with no documentation:update, matching the old release.sh; test:unit as the unit task) are correct. Security posture is strong — least-privilege permissions, untrusted commit message routed through an env var, injection-safe gh pr merge. The findings below are dominated by plan concerns (issues inherited verbatim from the plan's authoritative YAML that should be revisited fleet-wide, not defects of this diff) plus two trivial in-scope suggestions.

Cross-Cutting Themes

  • queue: max is not a valid GitHub Actions concurrency key (flagged by: correctness, code-quality) — GHA concurrency supports only group and cancel-in-progress; queue is silently ignored (or may fail schema validation), so the documented "keep every queued run" serialisation does not hold. This is copied verbatim from the family plan §4.2, so it is a plan concern affecting every migrated repo — the plan's YAML should be corrected — not a defect introduced by this PR.

Strengths

  • ✅ skip-ci-check reads github.event.head_commit.message into an env var and matches with bash [[ ]] — the correct defence against Actions script injection.
  • ✅ Least-privilege permissions: top-level contents: read, write elevated only on prerelease/release and the dependabot merge job.
  • ✅ Dependabot merge passes the PR URL via env, gates on the non-spoofable github.actor plus needs: [check, test, build].
  • ✅ RubyGems credential copied only after git-crypt unlock and locked to chmod 0600; pull_request (not pull_request_target) keeps secrets away from fork PRs.
  • ✅ Require and dev-dependency lists kept strictly alphabetical; dead CircleCI machinery removed wholesale.

General Findings

  • 🔵 Safety: Neither prerelease nor release sets timeout-minutes, so a hung step holds the main concurrency slot for up to GHA's 6-hour default and queues every later run behind it. The plan's YAML omits timeouts entirely — a plan concern worth raising fleet-wide.
  • 🔵 Security (suggestion, no action): prerelease/release unlock git-crypt and copy the RubyGems credential in the same job that runs ./go release. Acceptable under the current push: [main] trigger model; would become an exfiltration path only if that trigger were ever widened to untrusted refs.

Review generated by /accelerator:review-pr

concurrency:
group: main
cancel-in-progress: false
queue: max

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔴 Correctness / Code Quality — PLAN CONCERN, not a defect of this diff

The top-level concurrency block sets queue: max, but native GitHub Actions concurrency only supports group and cancel-in-progress. queue is not a recognised key, and the accompanying comment's claim that it "keeps every queued run" does not match GHA semantics: with cancel-in-progress: false, GHA allows exactly one pending run per group and cancels any older pending run when a newer one queues — there is no built-in full-queue retention.

Impact: At best the key is silently ignored and the intended anti-race serialisation does not fully hold — close-together merges can drop a queued prerelease. At worst a stricter workflow parser rejects the unknown key and the entire main pipeline fails to run.

This is reproduced verbatim from the Variant A family plan §4.2, so it applies to every migrated repo. It should be fixed in the plan rather than as a one-off deviation here; it does not block this plan-conformant diff. If genuine full-queue retention is required, it must be implemented outside the concurrency block.

# as of approval time, not the tested SHA — parity with the old
# release.sh, which also pulled. prerelease.sh never pulled, so the
# prerelease job deliberately has no pull.
run: git pull

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔵 Safety — PLAN CONCERN

The release job runs git pull before Set CI git author, with no pull strategy (--ff-only/--rebase). If main has diverged at approval time, git pull may attempt a merge commit with no git identity set, or fail on divergent branches. Serialisation via the concurrency group makes divergence unlikely, and the job fails safe (before publishing) if it happens.

This ordering and the bare git pull come straight from the plan's §4.2 YAML, so it is a plan concern (consider git pull --ff-only, and/or moving author config earlier, fleet-wide), not a defect of this diff.

Comment thread Rakefile
# Failures go to builds, not team-dev (org default), to keep noise
# out of a popular channel while this pipeline beds in.
{ when: {},
channel: 'C023XUE76GH', format: :failure } # builds

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔵 Standards

The catch-all routing rule's trailing comment uses a single space before # (format: :failure } # builds), whereas the four preceding rules use two spaces, and the plan's authoritative YAML aligns this comment with two spaces. A genuine (trivial) deviation from the plan. Suggest: format: :failure } # builds.

@phelma
phelma marked this pull request as ready for review July 14, 2026 09:07
Copilot AI review requested due to automatic review settings July 14, 2026 09:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Migrates the repository’s CI/CD pipeline from CircleCI to GitHub Actions, replacing CircleCI-specific scripts/config with GitHub Actions workflows and updating Rake provisioning/tasks to manage GitHub secrets/environments and Slack notifications.

Changes:

  • Added GitHub Actions workflows for PR and main branch runs (checks/tests/builds, prerelease/release flow, dependabot auto-merge).
  • Removed CircleCI configuration and related CI shell scripts/tooling.
  • Updated Rake tasks and gem dependencies to drop CircleCI/SSH tooling and add Slack/GitHub Actions-oriented provisioning.

Reviewed changes

Copilot reviewed 19 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
scripts/ci/steps/test.sh Removed CircleCI test step wrapper.
scripts/ci/steps/release.sh Removed CircleCI release step wrapper.
scripts/ci/steps/prerelease.sh Removed CircleCI prerelease step wrapper.
scripts/ci/steps/merge-pull-request.sh Removed CircleCI dependabot merge step wrapper.
scripts/ci/steps/build.sh Removed CircleCI build/check step wrapper.
scripts/ci/common/install-slack-deps.sh Removed CircleCI runner package install helper.
scripts/ci/common/install-gpg-key.sh Removed CircleCI GPG decrypt/import helper.
scripts/ci/common/install-git-crypt.sh Removed CircleCI git-crypt install helper.
scripts/ci/common/install-asdf.sh Removed CircleCI asdf install helper.
scripts/ci/common/install-asdf-dependencies.sh Removed CircleCI asdf dependency install helper.
scripts/ci/common/configure-rubygems.sh Removed CircleCI RubyGems credential setup helper.
scripts/ci/common/configure-git.sh Removed CircleCI git identity configuration helper.
scripts/ci/common/configure-asdf.sh Removed CircleCI asdf plugin configuration helper.
README.md Updated CI key management docs to reference .github/gpg.private.enc.
Rakefile Switched provisioning from CircleCI to GitHub secrets/environments; added Slack tasks and a build task.
rake_dependencies.gemspec Replaced CircleCI/SSH dev dependencies with rake_slack.
Gemfile.lock Updated dependency graph consistent with CI migration/dependency changes.
.github/workflows/pr.yaml New PR workflow running check/test/build and dependabot auto-merge.
.github/workflows/main.yaml New main workflow running check/test and prerelease + gated release.
.github/gpg.private.enc Added encrypted GPG key blob under .github/ for CI unlock flow.
.circleci/config.yml Removed CircleCI pipeline configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +17
concurrency:
group: main
cancel-in-progress: false
queue: max
Comment thread Rakefile
Comment on lines +124 to +126
# Operator's ambient auth — the stored PAT is gone (see the cutover PR's
# deliberate-decisions list).
t.access_token = ENV.fetch('GITHUB_TOKEN') { `gh auth token`.strip }
@phelma phelma closed this Jul 14, 2026
@phelma
phelma deleted the gha-migration branch July 14, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants