Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Releasing: CI, the release and publish workflows

Part of the developer notes; the index is in CLAUDE.md.

Releasing

  • CI (.github/workflows/ci.yml, every PR and push to dev/main): app build and type check, cargo clippy -D warnings, cargo test for core and oppx, ruff (syntax and undefined names) for the gateway, frontend and scripts, the connector tests (scripts/test_connectors.py), the agent tests with the document packages, the gateway’s input check (scripts/test_inputs.py), the regex check (scripts/check_regexes.py: every pattern compiled and proven free of catastrophic backtracking, with its own tests in scripts/test_check_regexes.py), shell syntax, and the PR title check for PRs into main. The GPU lifecycle test stays manual.
  • Release (.github/workflows/release.yml, a merged PR into main with a release prefix, or run by hand with a bump and a changelog line): scripts/bump_version.py bumps every version file and adds the CHANGELOG.md entry. The workflow commits “Release vX.Y.Z” to main, tags it, fast-forwards dev when possible, then starts Publish for the tag.
  • Publish (.github/workflows/publish.yml, workflow_dispatch with a tag; redo a release with gh workflow run publish.yml --ref main -f tag=vX.Y.Z). It always runs on main (the github-pages environment only deploys from main) and checks out the tag for everything it builds:
    1. oppx for x86_64/aarch64-unknown-linux-musl (static) and aarch64/x86_64-apple-darwin, with OPPX_RELEASE_TARGET set; oppxs for the two Linux musl targets; the app as .deb (with oppxs and its unit inside: beforeBundleCommand builds it, bundle.linux.deb.files places it) and AppImage (no oppxs: an AppImage is one file).
    2. A GitHub release with the archives, SHA256SUMS, install.sh, install-server.sh, and notes from main’s CHANGELOG.md.
    3. The docs (scripts/gen_docs.py + mdBook) on GitHub Pages.
  • No backend image is published. The app carries the build files (docker::BUILD_FILES: the Dockerfile, gateway.py, inputs.py, reqtrace.py, searchguard.py, start-sglang.sh, supervisord.conf, embedded with include_str!) and builds openphalanx-backend:<version>-<hash of those files> on the first start (docker::ensure_image): Docker pulls the official SGLang image, pinned by digest in the Dockerfile, and adds ~45 MB on top. A changed gateway or Dockerfile gets a new tag and is rebuilt, so neither users nor developers run a stale image; older tags of ours are removed after a build. Cluster members build the same way when a split worker starts. A custom image setting is pulled instead.
  • Why two workflows: a tag pushed with GITHUB_TOKEN starts no workflow, but a dispatch does. A pull_request run can’t deploy Pages, because its ref is the PR’s merge ref, not main.
  • One-time repository settings: Actions → workflow permissions “Read and write”; Pages → source “GitHub Actions”. main is protected by a ruleset (PR and status checks required), and GITHUB_TOKEN can’t be put on a ruleset’s bypass list, so the release commit is pushed with a write deploy key: its private half is the RELEASE_DEPLOY_KEY secret (ssh-key on the checkout in release.yml), and “Deploy keys” is a bypass actor on the ruleset. Without it the push is refused with GH013 (v0.6.0’s first run: the admin’s bypass covers the merge, not the workflow’s push). The commit and tag are pushed with --atomic, so a refusal leaves no tag.
  • Dependency updates (Renovate, renovate.json): the Mend Renovate app opens update PRs into dev (never main, where each PR would need a release prefix and cut a release), titled [chore] …, once a release has been public for 7 days (minimumReleaseAge, with internalChecksFilter: strict so no PR appears before that). Minor and patch updates come grouped in one PR, majors one each, and the lock files are refreshed on Monday mornings; nothing auto-merges. The SGLang base image digest gets its own PR (labelled needs-gpu-test: it changes every backend tag, so run the lifecycle example before merging), as do the pinned recheck/typescript of the regex check. The CI runtimes are deliberately at the bottom of the supported range and Renovate leaves them alone: Python 3.12 (the private Aider engine), Node.js 20, and ubuntu-22.04 for the .deb/AppImage builds. The Dependency Dashboard issue lists what is pending or held back. Check a change to the file with npx --package renovate renovate-config-validator --strict renovate.json.
  • Lint the workflows locally with docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest.