Continuous integration#
odx runs the same in CI as on a laptop. A CI job usually does three things:
build exactly what odx.lock pins, check that the checkout matches it, and
check the addons. A nightly job can run the bot that keeps the upstream
dashboard and opens pull requests.
GitHub Actions and Forgejo Actions#
getfluvo/odx-action is a
composite action for both. It installs odx from PyPI (version, default
latest; none keeps the odx on PATH) and restores the odx cache, keyed
on odx.lock, so a run fetches only what changed since the last one.
# .github/workflows/odx.yml
on: pull_request
permissions:
contents: read
jobs:
odx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: getfluvo/odx-action@v1
- run: odx aggregate --locked
- run: odx verify
- run: odx check-deps
- run: odx lint --only-changed --output-format github
odx aggregate --lockedfails whenpyproject.tomlchanged withoutodx lock; withCIset, locked commits come from the vault first.odx verifychecks, without network, that the working trees and lockfiles are whatodx.lockdescribes (Verifying a checkout).odx lint --output-format githubshows findings as annotations on the pull request;sariffeeds code scanning instead.odx status --fail-on-mergedfails when a carried pull request was merged upstream and the override can be dropped.
On Forgejo the workflow lives in .forgejo/workflows and uses
https://github.com/getfluvo/odx-action@v1: actions hosted elsewhere need
the full URL.
Other CI systems#
Install odx with uv or pip and keep ODX_CACHE_DIR in the CI system’s
cache. On GitLab CI:
odx:
image: python:3.13-slim
variables:
ODX_CACHE_DIR: "$CI_PROJECT_DIR/.odx-cache"
cache:
key:
files: [odx.lock]
paths: [.odx-cache]
before_script:
- apt-get update && apt-get install -y --no-install-recommends git
- pip install odx-cli
script:
- odx aggregate --locked
- odx verify
GitLab sets CI, so locked commits come from the vault first here as well.
The vault and the forge API need a token; pass it as ODX_TOKEN_<HOST>
(see Forge tokens).
Pre-commit#
The same checks run before a commit with the hooks described in Pre-commit hooks.
The nightly bot#
With bot: true, the action runs odx --json bot run: with --apply on a
schedule, as a plan on pull_request events (apply: auto). Its outputs are
version, plan-json, the path of the run’s JSON, and dashboard-issue.
What the bot does is described in
The dashboard as an issue.
# .github/workflows/odx-bot.yml
on:
schedule: [{ cron: "10 2 * * *" }]
issues: { types: [edited] }
workflow_dispatch:
permissions:
contents: read
jobs:
bot:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
id: app
with:
app-id: ${{ vars.ODX_APP_ID }}
private-key: ${{ secrets.ODX_APP_KEY }}
- uses: actions/checkout@v5
with:
token: ${{ steps.app.outputs.token }}
fetch-depth: 0
- uses: getfluvo/odx-action@v1
id: odx
with:
bot: true
env:
GITHUB_TOKEN: ${{ steps.app.outputs.token }}
- uses: actions/upload-artifact@v4
with:
name: odx-plan
path: ${{ steps.odx.outputs.plan-json }}
On Forgejo the same workflow lives in .forgejo/workflows, uses
https://github.com/getfluvo/odx-action@v1 (actions hosted elsewhere need the
full URL), and takes the token of a bot account from a secret instead of
the app; Forgejo’s upload-artifact stays at its v3-compatible version.
Platform |
Identity |
Why |
|---|---|---|
GitHub |
a GitHub App installed on the organization; the workflow mints an installation token ( |
pull requests opened with the default |
Forgejo, Gitea |
a bot account with a token scoped to the project repository |
there is no app API; workflows run on the bot’s pushes |
The token needs contents: write, pull-requests: write, issues: write
and metadata: read on the project repository, nothing on upstream
repositories (public ones are read without a token), and no rights on the
deployment repository or the package index: merging and promoting stay a
person’s. Pushing to the mirror uses its own credential (a separate secret,
ODX_TOKEN_<HOST>), write-only on the vault. odx reads tokens from the
environment and never prints them.