Files
copr-publish/CLAUDE.md
rob thijssen d690d5aaa8 fix: bound every copr-cli call so a hung request cannot stall the job
v1.0.3's heartbeat did its job: it showed exactly where the next failure was.
In monsoon run 56 the heartbeats stopped at 14m29s while the build was still
running, and the job was killed at 10:20:50 — five minutes after COPR had
reported success at 10:15:20.

The poll loop was blocked inside `$(build_state ...)`. copr-cli makes network
requests with no internal timeout, so `copr-cli status` hung for over twelve
minutes and took the loop with it. Same failure mode as watch-build in run 48,
just relocated.

Bound all of them: status (60s), submit (600s), download-build (600s), each
overridable. A timed-out status yields empty output, which reads as
non-terminal, so the loop simply heartbeats and retries on the next tick.

Test reproduces it with a stub whose first status call hangs; it deadlocks
against v1.0.3 and passes here.
2026-08-07 13:25:50 +03:00

4.8 KiB

CLAUDE.md

Context for future agent work in this repo. The README covers consumer-facing usage; this file covers things that aren't obvious from reading the code.

Where this action runs

Hosted on a self-hosted Gitea at git.lair.cafe (remote name origin, SSH url gitea@git.internal:actions/copr-publish.git). Consumers reference it by fully-qualified URL (uses: https://git.lair.cafe/actions/copr-publish@v1) because Gitea's DEFAULT_ACTIONS_URL points at github.com.

There is no CI on this repo itself. The action is tested by running it from a consumer repo (primarily helexa/cortex). When debugging, fetch job logs via the gitea-mcp tools against the consumer repo, not this one — e.g. mcp__gitea-mcp__actions_run_read with owner=helexa, repo=cortex.

tests/test-copr-build.sh covers the parts that don't need a real COPR: it puts a stub copr-cli on PATH and drives scripts/copr-build.sh through succeeded / failed / canceled / skipped / never-settles, plus the case where watch-build hangs. Run it directly (./tests/test-copr-build.sh); it needs nothing but bash and takes a few seconds.

Why the verdict does not come from watch-build

copr-cli watch-build holds a long-lived connection and can stop responding while the build carries on and finishes normally. Observed in monsoon run 48: COPR build 10835047 succeeded at 08:30:30, the watcher went silent after 08:09:11, and the runner killed the step at 08:35:50 — reported as a failed build, which also blocked the dependent version-bump job.

So the script polls copr-cli status for the authoritative state and treats watch-build purely as a progress stream whose exit code is ignored. If you are tempted to simplify this back into if copr-cli watch-build; then, don't.

Two rules learned the hard way

Bound every copr-cli call. They make network requests with no internal timeout and will block forever. This bit us twice in different places — watch-build in run 48, then status in run 56, where the poll loop sat inside a command substitution for twelve minutes and the job was killed five minutes after COPR had reported success. Any new copr-cli invocation gets a timeout.

Never leave the step silent. A long build produces no COPR output between state transitions, and a silent step is killed by the runner's inactivity timeout. The poll loop prints a heartbeat every interval; that is load-bearing, not decoration.

Consumers should pin an immutable tag

The runner caches actions by ref, so moving the floating v1 does not invalidate it — monsoon ran three releases against a stale cached copy after v1 had been moved to the fix. v1 is still maintained for convenience, but consumers that need a specific fix must pin vX.Y.Z.

Tagging & release workflow

We use a floating major tag alongside specific semver tags:

  • v1.0.0, v1.0.1, ... — immutable, annotated, per release.
  • v1 — floating annotated tag, moved forward to the latest v1.x.y on every v1 release. This is what @v1 consumers resolve to.

When cutting a patch/minor release within v1:

git tag -a v1.0.N -m "v1.0.N\n\n<summary>" <sha>
git tag -a v1 -f -m "v1 (floating): latest v1.x release" <sha>
git push origin main v1.0.N
git push origin v1 --force     # floating tag move requires --force

The --force on the v1 push is expected and authorized — that's how the floating tag works. Do not force-push main or immutable vX.Y.Z tags.

A v2 is reserved for the live-streaming behaviour change tracked in issue #1 (stdout timing differs enough that consumers should opt in). Do not quietly land that on v1.

The COPR builder-live.log gotcha

The on-mirror file served by copr-cli download-build is builder-live.log.gz for completed builds — not plain builder-live.log. The script in scripts/copr-build.sh handles both, preferring .gz with zcat. If you add log handling for other COPR artifacts (build.log, root.log, backend.log), assume they are gzipped too.

The HTTP live endpoint at https://download.copr.fedorainfracloud.org/results/<owner>/<project>/<chroot>/<build_id>-<pkg>/builder-live.log serves plaintext during and after the build — that's the path issue #1's live-streaming approach would use, sidestepping the .gz-on-disk issue.

Testing a change

There is no local harness. To verify a change end-to-end:

  1. Commit + push + tag as above (or push a branch and reference it by commit SHA from the consumer).
  2. Trigger a workflow in the consumer repo (e.g. push to helexa/cortex).
  3. Inspect the job log via gitea-mcp — note that job logs come back base64-ish wrapped in a JSON envelope and with \r/\n escapes; pipe through jq -r '.[].text' then sed 's/\\r/\n/g; s/\\n/\n/g' to get something greppable.

Syntax-only check locally: bash -n scripts/copr-build.sh.