Releasing lgtmaybe (maintainers)¶
Releases are automated by release-please (.github/workflows/release-please.yml).
It reads the conventional commits merged to main and keeps a "Release PR"
open that bumps the version and regenerates CHANGELOG.md. Merging that PR is
the release: it cuts the tag and the GitHub release, then the same run publishes —
PyPI via trusted publishing (OIDC) and the GHCR image + floating major
tag (v{major}, currently v2) via the reusable .github/workflows/release.yml
(built-in GITHUB_TOKEN). No publish tokens live in secrets.
A third workflow, .github/workflows/homebrew.yml, regenerates the Homebrew
formula in the tap repo (MattJColes/homebrew-tap, i.e. the tap
MattJColes/tap) so brew install MattJColes/tap/lgtmaybe tracks the latest
version.
scripts/update-homebrew-formula.sh writes a small formula that creates a venv
and pip installs lgtmaybe + its dependencies from PyPI wheels, with no
per-dependency resource stanzas. litellm's tree includes Rust sdists
(tokenizers, hf-xet) that can't build in Homebrew's sandbox, so building from
source is a dead end — the wheels work. The formula declares preserve_rpath
so Homebrew keeps the wheels' @rpath extension-dylib ids instead of failing to
rewrite them ("Failed to fix install linkage"). It's a plain source formula —
no bottle — so it installs on any architecture and macOS version.
The release run also calls .github/workflows/windows-exe.yml to build and
smoke-test a portable Windows executable, then .github/workflows/winget.yml
to submit the new asset to winget. The executable must exist before the winget
job starts; both workflows are manually dispatchable for recovery.
The workflow:
- Is called by
release-please.yml(workflow_call) right after a release, because arelease: publishedevent is not delivered for a release release-please cuts with the built-inGITHUB_TOKEN(GitHub suppresses downstream triggers fromGITHUB_TOKENto prevent recursion). A dailyscheduleand a manualworkflow_dispatch(with an optionalforceto re-publish the same version) are the safety nets. - Installs the regenerated formula in CI before committing it — a real
brew trust+brew installof the formula gates the push, so a formula that doesn't install is never published. The gate trusts the tap rather than settingHOMEBREW_NO_REQUIRE_TAP_TRUST, so it exercises the same two steps the install guide gives users. (You can seed/verify it by hand on a Mac by runningscripts/update-homebrew-formula.sh <version> path/to/Formula/lgtmaybe.rb.)
Net effect: a new version lands in the tap within minutes of release — no PyPI
cooldown to wait out, since brew update-python-resources (which imposes one)
isn't used.
Commit messages must follow conventional-commit format — .github/workflows/commitlint.yml
enforces it on PRs so release-please can compute the next version.
The only human-only pieces:
Contents¶
One-time setup¶
- On PyPI, add a trusted publisher for this repo: workflow
release-please.yml, environmentpypi(noPYPI_TOKENsecret — auth is via OIDC). The publish job is inline in that workflow on purpose: PyPI trusted publishing requires the OIDCjob_workflow_refto equal the top-level workflow. - Create the repo environment named
pypi(Settings → Environments). - After the first release, set the GHCR package visibility to public so
consumers can
docker pullthe image (Packages → lgtmaybe → Package settings). - First release only: from the GitHub release page, tick "Publish this Action
to the GitHub Marketplace", accept the terms, and pick the categories
code-reviewandcontinuous-integration. - Homebrew tap: create the repo
MattJColes/homebrew-tapwith aFormula/directory (it can start empty — the workflow writes the formula). Homebrew strips thehomebrew-prefix, so that repo is the tapMattJColes/tapand the formula inside it isMattJColes/tap/lgtmaybe— name the repohomebrew-tap, nothomebrew-lgtmaybe, or the formula reads asMattJColes/lgtmaybe/lgtmaybe. Add a repo secretHOMEBREW_TAP_TOKENto this repo: a fine-grained PAT withcontents: writeon the tap repo (the defaultGITHUB_TOKENcannot push to another repository). To seed or verify the formula by hand on a Mac, runscripts/update-homebrew-formula.sh <version> path/to/homebrew-tap/Formula/lgtmaybe.rb. - winget: fork
microsoft/winget-pkgs. Create a classic GitHub PAT with thepublic_reposcope and add it to this repo asWINGET_TOKEN. For the first release, build/upload the Windows asset and runwingetcreate new <asset-url>manually with package idMattJColes.lgtmaybe, installer typeportable, command aliaslgtmaybe, and MIT licence metadata. Microsoft moderates a new package before it exists; this can take days or weeks. Until that first manifest is accepted, the automatic release job warns and skips the winget update rather than failing the other release artifacts. Once accepted, the workflow useswingetcreate updatefor every later version.
Each release¶
- Merge feature/fix PRs to
mainusing conventional-commit messages (feat:,fix:,feat!:/BREAKING CHANGE:for a major bump). - release-please opens or updates the Release PR automatically. Review the proposed version + changelog, then merge it to publish.
- To (re)publish an existing tag to PyPI without a new release, run the
release-pleaseworkflow via workflow_dispatch with the tag name. - If Windows publication needs recovery, dispatch
windows-exefor the tag, then dispatchwingetafter the release asset is visible.
Rotate the public App private key¶
Rotate the public lgtmaybe GitHub App key quarterly, whenever maintainer access
changes, and immediately after any suspected exposure. Keep the old key active
until the replacement has passed a brokered smoke test so rotation does not
interrupt reviews.
- In the GitHub App settings, generate a new private key. Do not delete the old key yet.
-
From the directory containing the downloaded PEM, replace the retained Secrets Manager value without printing the key:
aws secretsmanager put-secret-value \ --secret-id lgtmaybe/github-app/private-key \ --secret-string file://lgtmaybe.private-key.pem \ --region ap-southeast-2 -
Refresh the Lambda configuration so every execution environment drops its cached copy of the old key:
FUNCTION_NAME="$( aws cloudformation describe-stack-resources \ --stack-name LgtmaybeGithubAppIdentity \ --region ap-southeast-2 \ --query "StackResources[?ResourceType=='AWS::Lambda::Function'].PhysicalResourceId | [0]" \ --output text )" aws lambda update-function-configuration \ --function-name "$FUNCTION_NAME" \ --description "GitHub App key rotated $(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --region ap-southeast-2 aws lambda wait function-updated-v2 \ --function-name "$FUNCTION_NAME" \ --region ap-southeast-2 -
Run a brokered dogfood review and confirm GitHub attributes it to
lgtmaybe[bot]. - Delete the old key in the GitHub App settings, then delete the downloaded PEM from the maintainer machine. Never paste the PEM into a command argument, issue, workflow log, or repository file.
If the smoke test fails, leave the old GitHub key active, put its PEM back into the same Secrets Manager secret, refresh the Lambda configuration again, and investigate before retrying.