gitea: add a CLI for reading CI runs and job logs #71

Merged
branderson merged 1 commits from gitea-actions into main 2026-08-04 14:19:39 -07:00
Owner

Summary generated by LLM agent

Adds forges/gitea/bin/gitea-actions, a read-only CLI for Gitea Actions
runs and job logs.

Why

runtime.bats builds the image and boots real containers, so it only ever
runs in CI — a dejima session has no Docker daemon by design. That means the
one suite a session cannot run is the one whose failures it most needs to
read, and until now the only route from a red build back into the session was
the operator opening the run in a browser and pasting the log in.

This came up the ordinary way: a runtime.bats test failed in CI on #69,
and getting at the reason took an operator round-trip.

Why a third CLI rather than gitea-pr checks

forges/gitea/bin/gitea-issue already establishes the shape — one
self-contained file per Gitea domain, deliberate copies rather than a shared
library, with the duplicated config/token/curl preamble pinned across them by
static.bats. Runs are also repo-scoped rather than PR-scoped: a push to
main has runs and no PR to hang them off.

Usage

gitea-actions runs [--limit N]
gitea-actions jobs [RUN]
gitea-actions logs [RUN] [--job NAME|INDEX] [--all] [--timestamps]

RUN is the number in the web URL (.../actions/runs/52). Left out, it
means the newest run for HEAD's commit — after a push, that is nearly
always the question. logs prints only the jobs that failed unless asked
otherwise, and strips the runner's per-line RFC3339 stamps unless
--timestamps is given, since that output usually lands in an agent's
context.

$ gitea-actions jobs 52
run 52:
0  success  sandbox (audit)
...
5  failure  sandbox (remote)
7  failure  host-docker

$ gitea-actions logs 52 --job host-docker | grep 'not ok'
not ok 33 a derived project image gets the package and keeps the base privilege posture

Two API details, neither discoverable

  • Runs are addressed by an internal id that no URL shows. The number in
    .../actions/runs/52 is run_number; the API wants the id field, which
    was 10306 for that run. resolve_run scans one page of the run list to
    map one to the other, so no caller has to know.
  • limit on the run list is ignored unless page is passed with it.
    ?limit=5 returned all 53 runs. Caught because runs --limit 4 printed
    everything.

The job index in jobs output is likewise not a field — it is the tail of
each job's html_url, and the list does not arrive in that order.

Read-only

It authenticates with the same write-scoped token as gitea-pr, so
"it only fetches runs and logs" is pinned as a property of the file:
static.bats fails the build if it ever grows a -X POST/PUT/PATCH/DELETE,
a --data or a -d. It is also added to the existing test that no gitea CLI
puts the token in argv.

Also

forges/gitea/forge.conf's install key names it, which is what makes
providers.bats check it exists and is executable. The Dockerfile needed no
change — it already installs everything under forges/*/bin/.

Suites run locally: static 161, providers 34, project 71, audit 139,
config 84. All green.

**Summary generated by LLM agent** Adds `forges/gitea/bin/gitea-actions`, a read-only CLI for Gitea Actions runs and job logs. ## Why `runtime.bats` builds the image and boots real containers, so it only ever runs in CI — a dejima session has no Docker daemon by design. That means the one suite a session cannot run is the one whose failures it most needs to read, and until now the only route from a red build back into the session was the operator opening the run in a browser and pasting the log in. This came up the ordinary way: a `runtime.bats` test failed in CI on #69, and getting at the reason took an operator round-trip. ## Why a third CLI rather than `gitea-pr checks` `forges/gitea/bin/gitea-issue` already establishes the shape — one self-contained file per Gitea domain, deliberate copies rather than a shared library, with the duplicated config/token/curl preamble pinned across them by `static.bats`. Runs are also repo-scoped rather than PR-scoped: a push to `main` has runs and no PR to hang them off. ## Usage ``` gitea-actions runs [--limit N] gitea-actions jobs [RUN] gitea-actions logs [RUN] [--job NAME|INDEX] [--all] [--timestamps] ``` `RUN` is the number in the web URL (`.../actions/runs/52`). Left out, it means the newest run for `HEAD`'s commit — after a push, that is nearly always the question. `logs` prints only the jobs that failed unless asked otherwise, and strips the runner's per-line RFC3339 stamps unless `--timestamps` is given, since that output usually lands in an agent's context. ``` $ gitea-actions jobs 52 run 52: 0 success sandbox (audit) ... 5 failure sandbox (remote) 7 failure host-docker $ gitea-actions logs 52 --job host-docker | grep 'not ok' not ok 33 a derived project image gets the package and keeps the base privilege posture ``` ## Two API details, neither discoverable - **Runs are addressed by an internal id that no URL shows.** The number in `.../actions/runs/52` is `run_number`; the API wants the `id` field, which was `10306` for that run. `resolve_run` scans one page of the run list to map one to the other, so no caller has to know. - **`limit` on the run list is ignored unless `page` is passed with it.** `?limit=5` returned all 53 runs. Caught because `runs --limit 4` printed everything. The job index in `jobs` output is likewise not a field — it is the tail of each job's `html_url`, and the list does not arrive in that order. ## Read-only It authenticates with the same write-scoped token as `gitea-pr`, so "it only fetches runs and logs" is pinned as a property of the file: `static.bats` fails the build if it ever grows a `-X POST/PUT/PATCH/DELETE`, a `--data` or a `-d`. It is also added to the existing test that no gitea CLI puts the token in argv. ## Also `forges/gitea/forge.conf`'s `install` key names it, which is what makes `providers.bats` check it exists and is executable. The Dockerfile needed no change — it already installs everything under `forges/*/bin/`. Suites run locally: static 161, providers 34, project 71, audit 139, config 84. All green.
branderson added 1 commit 2026-08-04 13:26:07 -07:00
gitea: add a CLI for reading CI runs and job logs
tests / sandbox (proxy) (pull_request) Successful in 2m1s
tests / sandbox (providers) (pull_request) Successful in 2m3s
tests / sandbox (static) (pull_request) Successful in 2m38s
tests / sandbox (config) (pull_request) Successful in 3m52s
tests / sandbox (remote) (pull_request) Successful in 4m57s
tests / sandbox (project) (pull_request) Successful in 5m17s
tests / host-docker (pull_request) Successful in 5m22s
tests / sandbox (audit) (pull_request) Successful in 7m4s
b3d32574a5
A sandbox has no Docker daemon, so the half of the suite that boots real
containers only ever runs in CI - and until now the only way to see why one
of those failed was for the operator to read the run in a browser and paste
it in. The session that wrote the code could not see what it did.

A third self-contained twin rather than a subcommand of gitea-pr: runs are
repo-scoped, not PR-scoped (a push to main has runs and no PR), and the
existing pair already establishes one file per Gitea domain with a
duplicated preamble pinned by static.bats.

Two things about the API are worth knowing and neither is discoverable:
runs are addressed by an internal id that no URL shows, and `limit` on the
run list is ignored unless `page` is passed with it. Both are handled here
so no caller meets them.

With no run given it resolves HEAD's commit - matched on head_sha, since a
pull_request run reports no branch at all - and prints only the jobs that
failed. It reads and never writes, which static.bats pins: it authenticates
with the same write-scoped token as gitea-pr.
branderson merged commit 70f01a8361 into main 2026-08-04 14:19:39 -07:00
Sign in to join this conversation.