docs: document CI status check and main branch protection
test / run tests/run_tests.py (pull_request) Failing after 31s

Add a Gitea Actions test-status badge plus a short README "CI" section
covering how to read the check and what to do when it's red. Capture
the (out-of-tree) branch-protection rule on `main` in docs/ci.md so
the gate that requires the test check is reproducible from the repo
alone — covers both the Gitea UI path and the equivalent API call.

Refs: PRD 0002

Assisted-by: Claude Code
This commit is contained in:
2026-05-08 20:21:54 -04:00
parent 6b140402dd
commit d0c2642943
2 changed files with 109 additions and 0 deletions
+26
View File
@@ -1,5 +1,7 @@
# claude-bottle
[![test](https://gitea.dideric.is/didericis/claude-bottle/actions/workflows/test.yml/badge.svg?branch=main)](https://gitea.dideric.is/didericis/claude-bottle/actions?workflow=test.yml)
Spins up an isolated container for running Claude Code with a curated set of skills and env vars.
## Why "claude-bottle"?
@@ -37,6 +39,30 @@ The container is removed automatically when the session ends. If the script
is killed with SIGKILL the exit trap won't fire and the container may be
left running; remove it with `docker rm -f <container-name>`.
## CI
Every push to a PR (and every push to `main`) runs the full test suite
on Gitea Actions via `.gitea/workflows/test.yml`. The status badge at
the top of this README links to the workflow's run history; the same
check appears on each PR.
Reading the check:
- Green — `tests/run_tests.py` exited 0 on the runner.
- Red — at least one test failed (or the workflow itself errored).
Click through to the run, expand the failing step, and read the
unittest output. Reproduce locally with `tests/run_tests.py` (or
`tests/run_tests.py unit` if you don't have Docker handy);
integration tests skip cleanly when Docker isn't reachable.
- Skipped tests — expected when the runner has no Docker daemon. They
still leave the job green; if you actually want them executed,
ensure Docker is available on the runner host.
Pushing a fix to a red PR re-triggers the workflow automatically — no
manual rerun needed. Branch protection on `main` requires this check
to be green before the merge button is enabled; see [`docs/ci.md`](docs/ci.md)
for how those rules are configured.
## Egress
Agent containers route HTTP / HTTPS traffic through a per-agent
+83
View File
@@ -0,0 +1,83 @@
# CI
The test workflow lives at [`.gitea/workflows/test.yml`](../.gitea/workflows/test.yml).
It runs `tests/run_tests.py` (full suite — unit + integration) on:
- every push to a branch with an open pull request, and
- every push to `main`.
Integration tests need Docker on the runner; they skip cleanly via
`tests/_docker.skip_unless_docker` when no daemon is reachable.
## Branch protection on `main`
Branch protection is **not** captured in tree — Gitea applies it via
the repo settings UI / API, not a checked-in config file. Reproducing
the rule on a fresh clone or migration therefore means re-applying the
same setting through one of the two paths below.
### Via the Gitea UI
1. Go to the repo on `gitea.dideric.is`.
2. **Settings****Branches****Branch protection rules**
**Add rule**.
3. **Branch name pattern:** `main`.
4. Enable **Enable Status Check** and select the check named
`test / run tests/run_tests.py` (the workflow's job display name).
The check has to have run at least once on the repo for Gitea to
list it; push a no-op commit on a feature branch first if needed.
5. (Recommended) Also enable **Require pull request before merging**
so changes to `main` always go through a PR — otherwise a direct
push to `main` bypasses the status check entirely.
6. Save.
After saving, open a PR. The "Merge" button should be disabled until
the test check is green.
### Via the Gitea API
Equivalent call (requires an admin token in `$GITEA_TOKEN`):
```sh
curl -X POST \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
https://gitea.dideric.is/api/v1/repos/didericis/claude-bottle/branch_protections \
-d '{
"rule_name": "main",
"enable_status_check": true,
"status_check_contexts": ["test / run tests/run_tests.py"],
"required_approvals": 0,
"block_on_outdated_branch": false
}'
```
The exact field for the check name is
`status_check_contexts` (an array of glob patterns). The Gitea
Actions check appears under
`<workflow-name> / <job-name>` — here `test / run tests/run_tests.py`.
Confirm the actual rendered context string in **Repo → Actions →
<run> → Job summary** before pasting into the API call; Gitea
versions occasionally tweak the formatting and a typo here silently
matches no checks (rule loads, but never blocks).
To inspect the live rule:
```sh
curl -H "Authorization: token $GITEA_TOKEN" \
https://gitea.dideric.is/api/v1/repos/didericis/claude-bottle/branch_protections
```
## Verifying the gate
Once the rule is in place, prove it works once with a deliberately-failing
test on a throwaway branch:
1. Create a branch (`gate-test-DELETEME`), add a test that fails (e.g.
`self.assertTrue(False)`), push, open a PR.
2. Wait for the check to go red. Confirm the "Merge" button is
disabled / shows the unmet status check.
3. Close the PR and delete the branch. Do not merge.
This is a one-time check after applying the rule, not a recurring
exercise.