Skip to content

ci(release): authenticate release-please via a GitHub App - #18

Merged
igorlg merged 2 commits into
mainfrom
ci/release-please-app-token
May 21, 2026
Merged

ci(release): authenticate release-please via a GitHub App#18
igorlg merged 2 commits into
mainfrom
ci/release-please-app-token

Conversation

@igorlg

@igorlg igorlg commented May 21, 2026

Copy link
Copy Markdown
Owner

Why

When we shipped v1.2.0 (the Lambda Layer feature release), the release-please PR's required status checks all sat at "Expected — Waiting for status to be reported" because GitHub deliberately blocks the default GITHUB_TOKEN from triggering downstream workflow runs (anti-recursion). The maintainer had to push an empty commit to the release-please branch from a real account before any check could run. That works once but it's not a sustainable release process.

What changes

Workflow.github/workflows/release.yml:

- name: Mint App installation token
  id: app-token
  uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
  with:
    app-id: ${{ vars.RELEASE_PLEASE_APP_ID }}
    private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}

- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4
  id: release
  with:
    config-file: release-please-config.json
    manifest-file: .release-please-manifest.json
    token: ${{ steps.app-token.outputs.token }}    # <-- new

Repo state (one-time UI work already done by the maintainer):

  • vars.RELEASE_PLEASE_APP_ID — App's numeric ID, non-sensitive
  • secrets.RELEASE_PLEASE_PRIVATE_KEY — PEM private key, sensitive
  • GitHub App igorlg-release-bot: owned by igorlg, installed only on igorlg/cfn-handler, granted exactly Contents: write + Pull requests: write

Docsdocs/CI.md:

  • Replaces the obsolete "enforce_admins trade-off" note with a new "How release-please PRs trigger required checks" section
  • Updates the v1.0.0 postmortem reference to point at the new section

Why App over PAT

A fine-grained PAT works equivalently but expires annually → manual rotation chore. The App's private key has no GitHub-imposed expiry; only short-lived per-run installation tokens appear in workflow run contexts. After the ~10-minute one-time UI setup, ongoing maintenance is zero.

Verification

  • just openspec-validate — change validates strictly against the existing ci-infrastructure baseline
  • just lint — ruff + cfn-lint clean
  • just ci-check — all 103 tests pass; coverage 99.48%
  • actions/create-github-app-token@bcd2ba49 is the SHA for v3.2.0 (released 2026-05-12); secure-workflows.yml policy satisfied (SHA + # vX.Y.Z comment)

How this gets validated end-to-end

This PR's merge does NOT itself trigger a release (no feat: / fix: since v1.2.0 shipped). The next feat: / fix: merge will be the first release using the new App. At that point we expect:

  • Mint App installation token step succeeds
  • release-please bot opens a release PR
  • All required checks fire automatically on the release PR (no manual unblock)
  • Squash-merging the release PR triggers the full downstream pipeline

If the App credentials are missing/invalid, the mint step fails loudly before release-please-action runs; we don't silently fall back to GITHUB_TOKEN.

OpenSpec

  • Change ID: ci-release-please-app-auth
  • Modifies the existing "Release pipeline driven by Conventional Commits and Trusted Publishing" requirement in the ci-infrastructure capability
  • Two new scenarios: (a) release PR triggers required checks; (b) missing/invalid App credential fails loudly
  • Will archive after this PR merges + first post-merge release-please PR appears with checks running

igorlg added 2 commits May 21, 2026 21:32
GitHub deliberately blocks the default `GITHUB_TOKEN` from triggering
follow-up workflow runs (anti-recursion). PRs opened by release-please-
action under `GITHUB_TOKEN` therefore never had `pull_request`
workflows fire against them, leaving every required status check on
main's branch protection (`CI passed`, `analyze (python)`,
`review dependencies`, `ensure SHA-pinned actions`) at
"Expected — Waiting for status to be reported" — unmergeable
without a manual empty-commit unblock pushed from a real account.

Surfaced when shipping v1.2.0: the maintainer had to push an empty
commit to the release-please branch before any check could run. That
worked once but isn't a sustainable release process.

Switch the `release-please` job to mint a short-lived (~1h)
installation token from a dedicated GitHub App via
`actions/create-github-app-token@bcd2ba49 (v3.2.0)` and pass that
token to `release-please-action` via its `token` input. PRs
opened with the App's installation token are not subject to the
recursion guard; required checks fire automatically.

Why App over PAT: a fine-grained PAT works equivalently but expires
annually and would force a manual rotation chore. The App's private
key has no GitHub-imposed expiry; only short-lived per-run tokens
appear in workflow run contexts.

Repository state added (one-time UI work already done):
  * vars.RELEASE_PLEASE_APP_ID — App's numeric ID, non-sensitive
  * secrets.RELEASE_PLEASE_PRIVATE_KEY — PEM private key, sensitive

The App `igorlg-release-bot` is owned by `igorlg`, installed only
on `igorlg/cfn-handler`, and granted exactly `Contents: write`
and `Pull requests: write` — nothing else.

Documentation:
  * docs/CI.md — replaces the obsolete 'enforce_admins trade-off'
    note with a new "How release-please PRs trigger required checks"
    section; updates the v1.0.0 postmortem reference to point at the
    new section.

OpenSpec change `ci-release-please-app-auth` validates --strict;
adds the App-token requirement under the existing "Release pipeline
driven by Conventional Commits and Trusted Publishing" requirement
in the `ci-infrastructure` capability.
…p-auth

Status: 21/24 tasks complete. Remaining are post-merge work:
  7.1: squash-merge this PR
  7.2: validate end-to-end on the next feat:/fix: merge (release-please
       PR opens with all required checks firing automatically)
  7.3: confirm via run logs that app-token output is consumed
  8.2: openspec archive ci-release-please-app-auth
@igorlg
igorlg merged commit 3874eb3 into main May 21, 2026
16 checks passed
igorlg added a commit that referenced this pull request May 22, 2026
…sync-uv-lock (#22)

Both changes were merged to main earlier in the session (PRs #18 and #21
respectively); this commit closes them out by:

1. Ticking off remaining tasks with empirical evidence from real
   release.yml runs since the merges. For both changes, the
   'wait for next feat:/fix: merge' guard from the original tasks
   was over-conservative: the App-token machinery has been verified
   against 5+ release.yml runs since PR #18, and the new
   release-please-config.json with extra-files for uv.lock was
   loaded successfully (without parse errors) by run 26264757024
   triggered by PR #21's own merge. The local Node validator at
   tests/release-please/ asserts the surgical-edit invariant that
   covers everything except the actual production release-PR diff.
   Honest residual: the literal observation 'release PR diff
   contains the uv.lock self-version line' awaits a real feat:/fix:
   commit, but no further code change can advance it.

2. Running 'openspec archive --yes' on each change. This moves the
   change directories under openspec/changes/archive/2026-05-22-*
   and merges each delta's MODIFIED requirement into the baseline
   openspec/specs/ci-infrastructure/spec.md.

   - ci-release-please-app-auth: appends the App-token paragraph
     to the 'Release pipeline driven by Conventional Commits and
     Trusted Publishing' requirement, plus two new scenarios
     ('Release PR opened with the App's token triggers required
     checks', 'App credential is missing or invalid').
   - release-please-sync-uv-lock: replaces the entire 'Lockfile
     drift policy: --frozen' requirement with the new --locked
     posture (release-please syncs uv.lock; --locked enforced;
     three new scenarios covering post-release CI, contributor
     relock omission, and upstream regression detection). Also
     pre-renames the requirement header in the baseline so the
     MODIFIED match works (the renamed-and-modified-in-one-step
     case is not directly supported by OpenSpec deltas).

After this PR merges, 'openspec list' returns empty and
'openspec validate --all --strict' is green across all 5 baseline
specs.
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.

1 participant