claudeandbit 66ef3f78d1 Say which runs reasserted the label, because it was not the schedule
The docstring credited the every-15-minutes cron with putting Status/Blocked
back after a hand-removal. It cannot have: schedule and `issues` events run the
workflow from the default branch, weblib-archive's default branch is `main`, and
`main` still carries a stale pre-org-label-migration copy at
tools/sync_blocked_label.py. That copy looks the label up with
`repos/{repo}/labels`; both repos now have zero repo-level labels (all 25 are
org-level), so it returns None and exits "skipped: no 'Status/Blocked' label in
this repo" without writing anything. Measured today: 377 scheduled and 54
`issues` runs, every one a no-op.

The reassertions came from pull_request and workflow_dispatch runs on branches
cut from `development`, which consume the shared script. The symptom tracks PR
activity, not a timer -- which also explains why it looked intermittent.

Mechanism and fix are unchanged; only the attribution was wrong, and a wrong
cause in a permanent comment is what sends the next reader to the wrong file.

Co-authored-by: bit <bit@das-labor.org>
2026-09-09 16:04:22 +00:00
2026-09-07 11:25:33 +00:00

weblib-ci

The CI scripts shared by cfbypass, weblib-archive, weblib-fs and weblib-viewer. Split out per weblib-archive#44, where they had been hand-copied into each repo and had already drifted once.

Public deliberately. Nothing here is a secret or specific to the archive's contents: a nixpkgs-pinning wrapper, a log poster and a label reconciler. Public means a consumer needs no deploy key, no ssh setup and no secret to fetch it — which was measured to be the difference between one step and three.

What is here

file what it does
with-nixpkgs.sh Runs a command with one nixpkgs package on PATH, pinned to the consuming repo's flake.lock. Avoids nix shell nixpkgs#x, which re-resolves the registry and refetches a channel tarball whenever the branch moves.
report_job_log.py Posts the tail of a build log as a PR comment. Exists because actions/jobs/{id}/logs returns 500 for every id on Gitea 1.25.2, so a red job otherwise says only that it failed. Takes --status; see below.
sync_blocked_label.py Keeps Status/Blocked in step with Gitea's dependency graph. Resolves the label from the repo or the organisation, never touches an issue marked Status/On Hold or Status/Abandoned, and leaves the label alone (rather than guessing) on a blocker whose state a repo-scoped token cannot resolve — see weblib-ci#11.

All three are standard library / plain bash only. They are run, not built, so this repo has no flake.

test_report_job_log.py covers the reporter and test_sync_blocked_label.py covers the reconciler. Both are standard library and offline — the forge each talks to is an http.server on localhost that keeps what it is sent, so a test reads state back rather than trusting an exit status of 0 (which the reporter in particular returns even when its POST failed).

python3 test_report_job_log.py       # 22 tests, ~1.2s, no network
python3 test_sync_blocked_label.py   # 12 tests, no network

There is no workflow running it: this repo has no .gitea/workflows at all, and no flake.lock for with-nixpkgs.sh to read. Run it by hand before pushing.

Using it

- uses: actions/checkout@v4
- id: ci
  uses: https://git.chaosbit.de/weblib/weblib-ci@main
- run: bash ${{ steps.ci.outputs.path }}/with-nixpkgs.sh python3 \
         python3 ${{ steps.ci.outputs.path }}/report_job_log.py /tmp/build.log

No credentials anywhere: the repo is public, which is the point of it being so.

with-nixpkgs.sh reads the consuming repo's flake.lock relative to the working directory, so it keeps working when invoked by absolute path from outside the checkout.

Use the full URL, not weblib/weblib-ci@main

Measured on weblib-archive#44 (2026-09-07), one job per form because Gitea posts one commit status per job and job logs return 500:

form result
uses: https://git.chaosbit.de/weblib/weblib-ci@main works
uses: weblib/weblib-ci@main fails
git clone https://…/weblib-ci.git with no credentials works
steps.<id>.outputs.path, then running a tool through it works

The bare owner/repo form resolves against the instance's default actions URL rather than this host, so it has to be the full URL. Both forms failed while this repo was private, which is the other half of why it is public — the alternative was a deploy key and an ssh setup step in four repos.

The outputs.path row is listed separately on purpose: the action running and its output reaching the caller are different claims, and a composite action returning an empty string is exactly the sort of thing that looks green.

report_job_log.py --status

The header used to be hardcoded to failed. That is true of every caller here, because each guards the step with if: failure() — but a probe run under if: always() posted a failure report for a job that had passed (weblib-viewer#10, filed as #8).

The default is still failed, so a caller passing only the log path is unchanged. A step that can run on success has to say so:

- name: report the log
  if: always()
  env:
    GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
  run: |
    bash "${{ steps.ci.outputs.path }}/with-nixpkgs.sh" python3 \
      python3 "${{ steps.ci.outputs.path }}/report_job_log.py" /tmp/build.log \
      --status "${{ job.status }}"

${{ job.status }} yields success/failure/cancelled/skipped, so those spellings are accepted alongside passed/failed. JOB_STATUS in the environment does the same thing if a flag is awkward. An unrecognised status is put in the header verbatim rather than rejected: argparse's choices= would exit 2 on a value this list has not heard of, and the log — the entire reason the script exists — would never be posted.

Why not a flake input

These are scripts a workflow runs, not derivations. A flake input would cost a flake.lock bump in four repos every time one changes, and buys nothing.

S
Description
No description provided
Readme
161 KiB
Languages
Python 96.5%
Shell 3.5%