Check Every Change Automatically, Release Versions and Tell the Lab | Heidelberg AI Curriculum
T19-L04
Check Every Change Automatically, Release Versions and Tell the Lab
Run an automatic check on every pull request with Gitea Actions, make it required so a failing change cannot be merged, keep the chat address as a secret, and publish numbered releases that announce themselves in the lab chat.
T19-L04 · Share & review code · Level 4 Integrator · 18-minute read, plus 2 hours of doing
Almost what you need
Since T19-L03 · Propose, Review and Merge Changes With Pull Requests, every change to the qPCR protocol is read by a second person. One slipped through anyway: a change lowered the cDNA to 4 µl but left the water unchanged, so the well held 19 µl instead of 20. The reviewer judged the idea, not the sum. Arithmetic is exactly what people overlook and programs don't. And when a new version is ready, the lab only hears about it if someone remembers to post in the chat.
In this book Gitea checks every pull request automatically, blocks the merge when the check fails, publishes approved versions as numbered releases, and tells the lab chat. You are at Level 4 Integrator: the repository now talks to the lab chat, so a mistake reaches people who never open Gitea.
After this you can
Run an automatic check on every pull request with Gitea Actions.
Make that check required, so a failing pull request can't be merged.
Send pull request and release events to the lab chat with a webhook.
Keep an address or password as a secret that a workflow can use but nobody can read back.
Publish an approved version as a numbered release that announces itself.
A runner. Actions need a runner, a computer that runs the checks. Ask IT to register one for your Gitea; step 1 shows how to see it.
Your chat's incoming webhook address (Mattermost, Slack, Teams or Matrix), from whoever runs the chat: a link that lets programs post into one channel. Treat it like a password.
About 2 hours 20 minutes: 18 to read, 2 hours to do.
Last verified 25 September 2026. Gitea Actions are free and built in, but they need a runner that your organisation (or you) operates. Details for experts at the end of this book.
The idea in one page
A workflow is a small file in the repository (in .gitea/workflows/) that says: when this happens, run these steps. Events are, for example, a pull request, a push to main, or a published release.
A runner does the work. Gitea sends it the job; the runner takes a fresh copy of the repository on a clean computer and runs the steps.
The result appears on the pull request as a check with a green tick or a red cross. A required check blocks the merge until it passes.
Programs check what can be counted; people judge what can't. The check adds up volumes; the reviewer asks whether 4 µl is sensible.
A webhook sends events out without any code, for example to the lab chat.
A secret is a value workflows can use but nobody can read back. Gitea hides it in the logs.
A release is a named version (a tag, such as v1.0) with notes, which colleagues can point to.
Step 1: Check that a runner is there. In the repository, click Settings, then Actions, then Runners.
You should see: at least one runner with the label ubuntu-latest and the status Idle (waiting for work). No runner? Ask IT; nothing in this book runs without one.
Worked example: a protocol that checks itself
Structure it
Step 2: Say what you want in one sentence.
When someone proposes a protocol change, I want a program to check the arithmetic and the plate layout before a person reviews it, and every approved version announced in the lab chat, instead of relying on everyone to notice.
Understand it
Step 3: Split the checking between program and person.
Question
Who checks it
Why
Do the reaction volumes add up to the total in the heading?
The program
Pure arithmetic
Is every well in the plate layout between A1 and H12, and used once?
The program
A fixed rule
Does every gene have a no-template control?
The program
A fixed rule
Is 4 µl of cDNA a sensible amount?
Jonas
Needs judgement
Choose tools
A Python check, written by OpenCode: Python is already on the runner, and OpenCode tests it for you.
A workflow plus branch protection: the check runs on every pull request and blocks the merge when it fails.
A webhook for pull request news in the chat, and a release workflow with a secret for the announcement.
Build it: the automatic check
Step 4: Let OpenCode write the check. In OpenCode, in the project's session, send:
Get the latest changes from Gitea (merge, don't rebase). Create a branch protocol-check from main and switch to it.
Write check_protocol.py, using only Python's standard library. It checks, and prints one clear line per problem:
1. In protocol.md, the volumes under "## Reaction mix (per well, 20 µl)" add up to the total in that heading.
2. In plate-layout.csv, every well is between A1 and H12, and no well appears twice.
3. Every gene in plate-layout.csv has at least one well whose sample is NTC.
It prints "All checks passed" and exits with 0, or lists the problems and exits with 1. Run it and show me the output.
Then create .gitea/workflows/check.yml: a workflow named check with one job named protocol. It runs on pull requests and on pushes to main, runs-on ubuntu-latest, checks out the code with actions/checkout@v4 and runs python3 check_protocol.py.
Commit both files with the message "Check the protocol automatically on every pull request" and push the branch protocol-check.
If Git reports a conflict, stop: don't resolve it yourself. Show me both versions of the lines and who wrote each.
You should see: that your real files pass all three checks, and the push of protocol-check. In our run OpenCode also tried the check on broken copies (the grey box); if yours doesn't, ask it to. On pushes to main checks main itself after every merge.
Step 5: Open the pull request and watch the check. Open a pull request for protocol-check with Jonas as reviewer, as in T19-L03. Within a minute the merge box shows the check.
You should see:check / protocol (pull_request) with a green tick. Click Details to read its log. The check has to run once like this before step 6 can find its name.
Jonas reviews the check itself (a check that always passes is worse than none), approves, and Mia merges.
Step 6: Make the check required.Settings → Branches → Edit on the rule for main. Tick Enable Status Check. Under Status checks found in the last week you see check / protocol (pull_request): type exactly that into Status check patterns. Click Save Rule.
You should see:Matched next to the check in the list. From now on a pull request whose check fails can't be merged, not even by you as owner (the last tick from T19-L03).
Build it: tell the lab
Step 7: Send events to the lab chat.Settings → Webhooks → Add Webhook → Slack (Mattermost accepts the same format; Teams and Matrix have their own entries). Fill in the chat's incoming address, the channel and a name. Under Trigger On, choose Custom Events… and tick Pull Request and Pull Request Reviewed. Releases get their own message in step 12. Click Add Webhook.
You should see: the webhook in the list. Open it and click Test Push Event at the bottom: a green tick under Recent Deliveries, and a test message in the chat.
Build it: a change the check catches
Step 8: Jonas proposes a change in the browser.(Jonas's screen.) He edits protocol.md on Gitea, lowers the cDNA from 5 to 4 µl and, because main is protected, chooses Create a new branch for this commit and start a pull request, with the branch name less-cdna. He clicks Propose file change, then Create Pull Request.
Step 9: Read the failure. The check runs by itself.
You should see: a red cross, Required, and a blocked merge. Click Details:
The log says exactly what's wrong: 19 µl, but the heading says 20 µl.
Step 10: Fix it on the same branch.(Jonas's screen.) He opens the branch menu above the file list, picks less-cdna, opens protocol.md and clicks the pencil. He raises the water from 3.4 to 4.4 µl and, at the bottom, keeps Commit directly to the less-cdna branch: Add 1 µl water so the well stays at 20 µl.
You should see: the check on pull request #4 turn green. Mia reviews whether 4 µl makes sense, approves, and merges.
Build it: numbered releases that announce themselves
Step 11: Store the chat address as a secret.Settings → Actions → Secrets → Add Secret. Name:LAB_CHAT_URL. Value: the chat's incoming address. Click Confirm.
You should see:LAB_CHAT_URL in the list, with the value shown as stars. Nobody, you included, can read it back; you can only replace it. Why a secret: anyone with the address can post in your chat. A webhook keeps its address in settings that repository admins can open; a secret is only ever used by a workflow. (The two addresses in the pictures differ because the test chat runs on this computer.)
Step 12: Let OpenCode write the announcement. Send:
Get the latest changes from Gitea (merge, don't rebase). Create a branch announce-release from main and switch to it.
Create .gitea/workflows/announce.yml: a workflow named announce with one job named post. It runs when a release is published, runs-on ubuntu-latest, and uses curl to POST this JSON to the address in the secret LAB_CHAT_URL: {"text": "Protocol <tag> released: <release name>. <release URL>"}. Never print the secret.
Commit it with the message "Announce each release in the lab chat" and push the branch announce-release.
If Git reports a conflict, stop: don't resolve it yourself. Show me both versions of the lines and who wrote each.
You should see: the push of announce-release; OpenCode also explains that the secret is never printed. Open a pull request; the check runs and passes; Jonas approves; Mia merges.
Step 13: Publish the release.Releases → New Release. Git Tag:v1.0, target main. Release title: the version and from when it's valid. Release notes: what changed. Click Publish Release.
You should see: the release with Stable and downloads of the whole repository at that version. Anyone can now say "we ran v1.0".
Step 14: Check the announcement.Actions shows a new run. It's named after the last merge on main; announce above it is the workflow. Its log reports the post without the address.
You should see:Posted the announcement to the lab chat (HTTP 200), and in the chat:
The release message is the workflow's, in your wording; below it, the webhook's pull request news.
Company: the onboarding checklist
Steps 4 to 6: a check that every item in onboarding.md names who does it and how many days before the start date.
Step 7: a Teams webhook to the HR–IT channel for pull requests.
Steps 8 to 10: a new item without an owner fails the check until the owner is added.
Steps 11 to 14: each release (2026-10) posts Onboarding checklist 2026-10 is valid from 1 October to the channel.
What goes wrong
The check stays at Waiting.Symptom: the pull request shows the check, but it never starts. Fix: no runner with the label ubuntu-latest is online. Look at step 1, then ask IT.
The rule doesn't know your check.Symptom: in step 6 the list shows your check without Matched. Fix: the pattern differs from the check's name. Copy it from Status checks found in the last week, character for character. Until it matches, Gitea doesn't enforce the check by name.
A check that always passes.Symptom: green on every pull request, including a broken one. Fix: try it on a deliberate mistake, as in step 8, before you rely on it. A pull request can change its own check, so reviewers read changes to .gitea/workflows/ and check_protocol.py as carefully as changes to the protocol.
The webhook shows a red cross.Symptom: under Recent Deliveries, a failed delivery. Fix: click it to read the response. Wrong address or channel: correct it. An address inside your network may need the Gitea administrator to allow it.
The secret appears in a log.Symptom: you see the address in plain text. Fix: Gitea stars out the exact value; changed forms of it (split, encoded) slip through. Ask the chat admin to delete that incoming address and make a new one (the old one works until it's deleted). Save the new one as the secret, then remove the line that printed it.
No Actions in the Settings menu.Symptom:Settings has no Actions entry and no workflow ever runs. Fix: Actions are switched off for this repository or the whole Gitea. Ask the administrator to turn them on.
The check fails at Run actions/checkout.Symptom: the first step fails before your check runs. Fix: the runner can't download the checkout action, which comes from GitHub by default. IT can point Gitea at a copy it can reach.
The release didn't announce.Symptom: no run of announce. Fix: the workflow must already be on main when you publish. A draft release doesn't count; only Publish Release triggers it.
Do it yourself (2 hours)
Set up (10 min): step 1 on your Gitea.
Structure it (5 min): your sentence: "When someone proposes ___, I want a program to check ___ before a person reviews it."
Understand it (15 min): your own table as in step 3. Only fixed rules go to the program.
Choose tools (5 min): Python for the check; your chat's webhook address.
Build it (85 min): steps 4 to 6, with OpenCode writing your checks; step 7; one deliberate mistake that the check must catch (steps 8 to 10); then steps 11 to 14 for your first release.
Exit check
A repository where a required automatic check runs on every pull request, one pull request was blocked by it and fixed, and publishing release v1.0 posted a message in the chat, using the secret address. It passes when a colleague can show you the red check, its log line naming the mistake, and the chat message, without asking you how.
Rule to remember
Let a program check what can be counted, so the reviewer's attention goes to what only a person can judge.
Details for experts
Checked on 25 September 2026. You don't need this to follow the book; it's for setting things up or answering a colleague.
What
Details
What it means for you
Versions
Gitea 1.27.2; the runner program act_runner 0.6.1 (the project now calls it Gitea Runner); in the pictures the label ubuntu-latest runs jobs in the container node:20-bookworm; OpenCode Desktop 2.0.16.
IT chooses the runner's containers; ask which labels exist.
Runners
Registered under Settings → Actions → Runners → Create new Runner (a token), at repository, organisation or instance level. On gitea.com, plan to register your own runner.
A runner runs whatever the workflows say: run it on a machine meant for that, not your laptop.
Workflow format
The same on:, jobs:, steps:, uses: as GitHub Actions; files in .gitea/workflows/ (.github/workflows/ also works). Actions such as actions/checkout are fetched from GitHub by default (DEFAULT_ACTIONS_URL).
Most GitHub Actions examples work unchanged.
Check names
workflow / job (event), here check / protocol (pull_request). Patterns in the rule may use *.
Renaming the workflow or job changes the name: update the rule too.
Secrets
Repository, organisation or user level; starred out in logs; not given to pull requests from forks. Variables (next to Secrets) are for values that aren't secret.
Put addresses with tokens in Secrets, plain settings in Variables.
Webhooks
Types: Gitea, Gogs, Slack, Discord, DingTalk, Telegram, Microsoft Teams, Feishu, Matrix, WeCom, Packagist. Mattermost accepts the Slack format. Internal addresses need the administrator's ALLOWED_HOST_LIST.
Pick your chat's type; the message format follows.
Releases
A tag plus notes; Save Draft or Pre-Release for tests; Generate release notes lists merged pull requests; files can be attached.
Attach the PDF of the protocol if the lab prints it.
Packages
A built-in registry (generic files, containers, Python, npm and more) under Packages; workflows can publish to it.
GitHub-hosted runners (limited free minutes for private repositories); Settings → Branches or Rules → Require status checks to pass; Settings → Webhooks; Secrets and variables → Actions; Releases → Draft a new release.
Same concepts; on GitHub you usually don't need your own runner.
T03-L04: Watch 7:04–11:35 for enabling Actions, running an example and creating a workflow—the mechanism behind automatic prompt tests. This sponsored 2024 tutorial does not implement the book's Promptfoo gate or rollback. Leave runner installation to an authorized operator.
T08-L05: Follow runner setup and a first Gitea Actions workflow to understand how automated checks get executed. Historical setup companion, not required-check enforcement, restricted AI review or rollback instruction.
T19-L04: Watch 7:04–11:35 for enabling Actions and creating and testing a workflow. This sponsored 2024 tutorial explains execution behind automatic checks, not the book's exact required-check name, secret handling or chat announcements. Runner setup belongs to an authorized operator.