Skip to content

Setting up each forge

llms.txtlists every page for an agent
Optional: hand this page to your coding agentThe steps work by hand too.
Show the whole prompt
Prepare this repository's pipelines for the forges I name, following https://intentius.io/sql-yodeler/forges/: set `ci.forges` in yodel.config.ts, run `npm run ci`, and check that `npx yodel ci --check` passes.
In the pull request description, list by name every secret, variable, environment, protection, token and runner the matrix says each forge needs. Put no secret's value in a file or in the description; I add them.
Open a pull request with the result.
Never run `yodel apply` against a shared environment, never run `chant approve`, `yodel approve` or `yodel override`, never edit the `chant/lifecycle` branch or `.chant/allowed_signers`, never merge; approvals and applies belong to people.

yodel ci (npm run ci in a project made from a starter template) renders the same jobs for GitHub Actions, GitLab CI and Forgejo Actions: lint and a plan comment per environment on each pull request, an apply wave per environment on a push to main, and a drift watch per environment on its schedule. Which job holds which credentials is the same on every forge (Least privilege on each forge). What differs is where you put the secrets, which tokens the jobs use, and what the runner needs:

GitHub GitLab Forgejo
Pipeline files .github/workflows/yodel-pr.yml, yodel-apply.yml, watch-<env>.yml .gitlab-ci.yml, .gitlab/yodel-apply.gitlab-ci.yml, .gitlab/yodel-watch.gitlab-ci.yml .forgejo/workflows/yodel-pr.yml, yodel-apply.yml, watch-<env>.yml
Readers’ secrets repository secrets CI/CD variables, masked, not protected (merge request pipelines run on unprotected branches), with no environment scope repository secrets
Writer’s secrets environment secrets of the environment named <env>, whose deployment branches are limited to main; only the wave job names that environment, so a pull request job cannot read them CI/CD variables, protected, masked and scoped to the environment <env>; only a job on a protected branch that declares that environment gets them, and only the wave job declares it repository secrets: Forgejo has no environments, so a workflow on any branch can name them. Limit who can push branches to those trusted to apply
The plan comment the job’s GITHUB_TOKEN, with pull-requests: write (the workflow grants it) GITLAB_TOKEN: a project access token with the api scope (Reporter); CI_JOB_TOKEN cannot write merge request notes the job’s own token (FORGEJO_TOKEN, else GITHUB_TOKEN)
The drift watch watch-<env>.yml on the watch’s cron; the job’s token with issues: write for the tracking issue no cron in the file: one pipeline schedule per watch, with the cron and CHANT_SCHEDULED_OP noted at the top of .gitlab/yodel-watch.gitlab-ci.yml; GITLAB_TOKEN for the tracking issue watch-<env>.yml on the watch’s cron; the job’s own token writes the tracking issue
Resuming after yodel approve re-runs the run’s failed jobs at the same commit; a fine-grained token with Actions: read and write retries the job, and the waves after it run when it passes; a project access token with the api scope and the Developer role (CI_JOB_TOKEN cannot retry a job) dispatches yodel-apply.yml again on the branch (no re-run API), so the waves before it plan again and apply nothing new; a token with write:repository
Cloud roles over OIDC the workflow grants id-token: write to a job that mints a cloud password the job declares an ID token for the cloud’s audience (AWS_ID_TOKEN, GCP_ID_TOKEN or AZURE_ID_TOKEN) none: Forgejo issues no OIDC token to a job, so the runner’s own cloud identity mints
Runner requirements ubuntu-latest; the lint job runs in a node:22-bookworm container with its replay server as a service, so a self-hosted runner needs Docker a runner that runs image: and services: (the Docker executor, say); the jobs run in node:22-bookworm an Actions runner with the docker label
Pull requests from forks they get no secrets and a read-only token: lint runs, but the plan jobs hold no reader and post no comment a merge request from a fork runs its pipeline in the fork’s project by default, with none of this project’s variables they get no secrets

yodel approve takes its token from CHANT_FORGE_TOKEN, else GITHUB_TOKEN or GH_TOKEN (GitHub and Forgejo), or GITLAB_TOKEN (GitLab), on your machine (Approve and resume). With ci: { apply: false } (Lint and plan on pull requests only) there is no apply pipeline and no writer’s secret on any forge, and the rows about the writer and resuming do not apply. A role whose password a cloud token source mints has no password variable; Short-lived tokens has the sources, and ci.login for exchanging the forge’s OIDC token.

The README a starter template writes into the project has the same steps, for reading offline.

  • Repository secrets: the readers’ variables and the URLs (DEV_CLICKHOUSE_URL, DEV_CLICKHOUSE_READER_USER, DEV_CLICKHOUSE_READER_PASSWORD for dev on ClickHouse; DEV_POSTGRES_... on Postgres).
  • Environments dev and prod (Settings > Environments > <env>), each with deployment branches limited to main, holding that environment’s URL and writer variables. Only the wave job names the environment, so pull request jobs cannot read its secrets. Pull requests from forks get no secrets at all.
  • The workflows set their own token permissions: contents: read and pull-requests: write on yodel-pr.yml for the comment, issues: write on each watch-<env>.yml for the tracking issue, and contents: write on yodel-apply.yml, which pushes the pending approval to chant/lifecycle.
  • The lint job runs in a node:22-bookworm container and reaches its replay server by the service’s name, so it needs no free port on the runner; a self-hosted runner needs Docker.

The scenario claims below run what this page describes against a real server, once plain (it passes) and once with the behaviour broken (the claim catches it). Claims status lists every claim.

Claim What it says Plain, broken Last run
template a project from the starter template, on Forgejo: apply only after approval, lint with replay and the plan comment on a pull request, and the approved change applied on merge; a sealed wave applies only on an approval sealed by a signer listed at the base, and a pr-review wave on the review of a writer other than the author; a pull request job cannot write, a forked migration fails lint and is annotated, a stale or hand-edited pipeline fails yodel ci –check, the CI image pinned by digest runs a pull request’s jobs, a command token source mints the reader’s password, and the drift watch keeps one tracking issue ClickHouse: pass, caught; Postgres: pass, caught 868ff97, 2026-10-10
template-github a project from the starter template, on GitHub Actions (act and a mock GitHub): apply only after approval, lint with replay and the plan comment on a pull request, and the approved change applied on merge; a sealed wave applies only on an approval sealed by a signer listed at the base, and a pr-review wave on the review of a writer other than the author; a pull request job cannot write, a forked migration fails lint and is annotated, a stale or hand-edited pipeline fails yodel ci –check, the CI image pinned by digest runs a pull request’s jobs, a command token source mints the reader’s password, and the drift watch keeps one tracking issue ClickHouse: pass, caught; Postgres: pass, caught 868ff97, 2026-10-10
template-gitlab not recorded

SQL Yodeler