detached ac92e76

docs: document CI in the README

ERMITE_OWNER and ERMITE_CI_LOG_RETENTION, the run and runner commands, the
runner token preset, and a short CI section: on once a runner is registered,
the merge rule, unsupported features, logs on disk, and a link to
ermite-runner. The help of --failing-checks-block no longer says there are no
checks.
commit
ac92e76fd15962ebbd4a39bc97f3cca7b3a79354
author
brice · · 2026-10-12 19:24 UTC
parent
081981c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +16 -3
M README.md +15 -2
@@ -27,6 +27,8 @@ ermite serve
2727 | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` |
2828 | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges |
2929 | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges |
30+| `ERMITE_OWNER` | `owner` | owner login that CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names |
31+| `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour |
3032
3133 `ermite version` prints the version (and the server's when `ERMITE_URL` is set).
3234
@@ -37,11 +39,14 @@ The same binary drives a running server over its API. It reads `ERMITE_URL` (the
3739 ```sh
3840 export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_...
3941 ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes]
40-ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once
42+ermite token create <label> [--preset cli|agent|ci|runner] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once
4143 ermite token list | revoke <id>
4244 ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff]
4345 ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment>
4446 ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n>
47+ermite run list <repo> [--workflow …] [--branch …] [--status …] | view <repo> <id> [--attempt n] [--log [--job …]]
48+ermite run watch <repo> <id> [--log] | cancel <repo> <id> | rerun <repo> <id> [--failed]
49+ermite runner list | remove <id>
4550 ermite web login # prints a one-time sign-in link (60 s)
4651 ermite web logout --all # signs every browser out
4752 ermite signing-key show # public key and allowed_signers line of squash commits
@@ -51,7 +56,7 @@ ermite watch [repo] [--json] # follows events, reconnects and resumes
5156
5257 An agent skill describing the CLI and the forge lives in [`skills/ermite`](skills/ermite/SKILL.md); link or copy that folder into your agent's skills directory.
5358
54-`--json` on `repo list|show|create|edit`, `token create|list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server.
59+`--json` on `repo list|show|create|edit`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server.
5560
5661 ## Git over HTTP
5762
@@ -66,6 +71,14 @@ Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; th
6671
6772 A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch).
6873
74+## CI
75+
76+ermite runs the `.github/workflows/*.yml` workflows of a repository on `push` (a branch or a tag, and the merge of a merge request) and shows each job as a check on the commit, the branch list and the merge request. CI is on once a runner is registered; there is no switch. Before that a push makes no run and no check.
77+
78+Runs execute on [ermite-runner](https://git.bnema.dev/bnema/ermite-runner) hosts, which call ermite over its API. Create a token for the runner host with `ermite token create <label> --preset runner` (the `runner:register` scope, which also gives access to the code and the secrets of every repository: give it only to runners you operate), then register the runner with it. `ermite runner list` shows the runners and whether they are online; `ermite runner remove <id>` stops one. A job goes to a runner that has every label of its `runs-on`; a job no runner takes within 24 hours fails.
79+
80+`ermite run list|view|watch|cancel|rerun` and the `Actions` tab of a repository read and control the runs; `cancel` and `rerun` need the `ci:write` scope. With `failing_checks_block` on (the default, `ermite repo edit --failing-checks-block=false`), a merge is refused while a check of the head failed or has not finished. A workflow that uses a feature the runner does not support yet (`uses:` steps, `container:`, `services:`, `environment:`, `concurrency`, `continue-on-error`) fails with `not supported yet: <feature>`, never silently. Logs are files under `$ERMITE_DATA_DIR/ci/logs`.
81+
6982 ## Web sign-in
7083
7184 `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming sets a session cookie for 12 hours (never longer than the token that asked for the link). It needs `https` in `ERMITE_PUBLIC_URL` (except on localhost). Browser writes require the session and an `Origin` equal to `ERMITE_PUBLIC_URL`. `ermite web logout --all`, or revoking a token with every scope, ends every session.
@@ -27,6 +27,8 @@ ermite serve
27 | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` |27 | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` |
28 | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges |28 | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges |
29 | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges |29 | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges |
30+| `ERMITE_OWNER` | `owner` | owner login that CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names |
31+| `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour |
30 32
31 `ermite version` prints the version (and the server's when `ERMITE_URL` is set).33 `ermite version` prints the version (and the server's when `ERMITE_URL` is set).
32 34
@@ -37,11 +39,14 @@ The same binary drives a running server over its API. It reads `ERMITE_URL` (the
37 ```sh39 ```sh
38 export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_...40 export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_...
39 ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes]41 ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes]
40-ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once42+ermite token create <label> [--preset cli|agent|ci|runner] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once
41 ermite token list | revoke <id>43 ermite token list | revoke <id>
42 ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff]44 ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff]
43 ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment>45 ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment>
44 ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n>46 ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n>
47+ermite run list <repo> [--workflow …] [--branch …] [--status …] | view <repo> <id> [--attempt n] [--log [--job …]]
48+ermite run watch <repo> <id> [--log] | cancel <repo> <id> | rerun <repo> <id> [--failed]
49+ermite runner list | remove <id>
45 ermite web login # prints a one-time sign-in link (60 s)50 ermite web login # prints a one-time sign-in link (60 s)
46 ermite web logout --all # signs every browser out51 ermite web logout --all # signs every browser out
47 ermite signing-key show # public key and allowed_signers line of squash commits52 ermite signing-key show # public key and allowed_signers line of squash commits
@@ -51,7 +56,7 @@ ermite watch [repo] [--json] # follows events, reconnects and resumes
51 56
52 An agent skill describing the CLI and the forge lives in [`skills/ermite`](skills/ermite/SKILL.md); link or copy that folder into your agent's skills directory.57 An agent skill describing the CLI and the forge lives in [`skills/ermite`](skills/ermite/SKILL.md); link or copy that folder into your agent's skills directory.
53 58
54-`--json` on `repo list|show|create|edit`, `token create|list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server.59+`--json` on `repo list|show|create|edit`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server.
55 60
56 ## Git over HTTP61 ## Git over HTTP
57 62
@@ -66,6 +71,14 @@ Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; th
66 71
67 A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch).72 A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch).
68 73
74+## CI
75+
76+ermite runs the `.github/workflows/*.yml` workflows of a repository on `push` (a branch or a tag, and the merge of a merge request) and shows each job as a check on the commit, the branch list and the merge request. CI is on once a runner is registered; there is no switch. Before that a push makes no run and no check.
77+
78+Runs execute on [ermite-runner](https://git.bnema.dev/bnema/ermite-runner) hosts, which call ermite over its API. Create a token for the runner host with `ermite token create <label> --preset runner` (the `runner:register` scope, which also gives access to the code and the secrets of every repository: give it only to runners you operate), then register the runner with it. `ermite runner list` shows the runners and whether they are online; `ermite runner remove <id>` stops one. A job goes to a runner that has every label of its `runs-on`; a job no runner takes within 24 hours fails.
79+
80+`ermite run list|view|watch|cancel|rerun` and the `Actions` tab of a repository read and control the runs; `cancel` and `rerun` need the `ci:write` scope. With `failing_checks_block` on (the default, `ermite repo edit --failing-checks-block=false`), a merge is refused while a check of the head failed or has not finished. A workflow that uses a feature the runner does not support yet (`uses:` steps, `container:`, `services:`, `environment:`, `concurrency`, `continue-on-error`) fails with `not supported yet: <feature>`, never silently. Logs are files under `$ERMITE_DATA_DIR/ci/logs`.
81+
69 ## Web sign-in82 ## Web sign-in
70 83
71 `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming sets a session cookie for 12 hours (never longer than the token that asked for the link). It needs `https` in `ERMITE_PUBLIC_URL` (except on localhost). Browser writes require the session and an `Origin` equal to `ERMITE_PUBLIC_URL`. `ermite web logout --all`, or revoking a token with every scope, ends every session.84 `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming sets a session cookie for 12 hours (never longer than the token that asked for the link). It needs `https` in `ERMITE_PUBLIC_URL` (except on localhost). Browser writes require the session and an `Origin` equal to `ERMITE_PUBLIC_URL`. `ermite web logout --all`, or revoking a token with every scope, ends every session.
M internal/app/cli_repo.go +1 -1
@@ -275,7 +275,7 @@ func newRepoEditCmd(env *cliEnv) *cobra.Command {
275275 Long: "Change the fields given as flags and leave the others alone; --description \"\" clears the\n" +
276276 "description. The default branch must already exist. --merge-modes lists the modes a merge request\n" +
277277 "may be merged with (fast-forward, squash; default for a new repository: both), and\n" +
278- "--failing-checks-block says whether a failing check prevents a merge (there are no checks yet).\n" +
278+ "--failing-checks-block says whether a failing check prevents a merge (a failed or unfinished CI check of the head; see README, CI).\n" +
279279 "Needs the repo:admin scope." + jsonRepoHelp,
280280 Args: cobra.ExactArgs(1),
281281 ValidArgsFunction: completeRepoNames(env),
@@ -275,7 +275,7 @@ func newRepoEditCmd(env *cliEnv) *cobra.Command {
275 Long: "Change the fields given as flags and leave the others alone; --description \"\" clears the\n" +275 Long: "Change the fields given as flags and leave the others alone; --description \"\" clears the\n" +
276 "description. The default branch must already exist. --merge-modes lists the modes a merge request\n" +276 "description. The default branch must already exist. --merge-modes lists the modes a merge request\n" +
277 "may be merged with (fast-forward, squash; default for a new repository: both), and\n" +277 "may be merged with (fast-forward, squash; default for a new repository: both), and\n" +
278- "--failing-checks-block says whether a failing check prevents a merge (there are no checks yet).\n" +278+ "--failing-checks-block says whether a failing check prevents a merge (a failed or unfinished CI check of the head; see README, CI).\n" +
279 "Needs the repo:admin scope." + jsonRepoHelp,279 "Needs the repo:admin scope." + jsonRepoHelp,
280 Args: cobra.ExactArgs(1),280 Args: cobra.ExactArgs(1),
281 ValidArgsFunction: completeRepoNames(env),281 ValidArgsFunction: completeRepoNames(env),

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close