.gitea/workflows/ci.yaml: pull_requesttypes: swaps ready_for_review for edited. One word; everything else in this PR is prose that had to stop being wrong.
The ~11-line comment above types: is rewritten and shortened — it currently diagnoses ready_for_review as inert and then prescribes the empty-commit workaround anyway.
Ride-along, same file: the concurrency: comment said "this Gitea (1.26.0)" while GET /version now returns 1.27.2 (upgraded 2026-08-25/26, per daniel/gitdan's runbook and gitdan#46). This is a currency correction, not a re-verification — the number was not simply swapped, because writing 1.27.2 would assert the cancellation behaviour was observed there, and it was not: the runs cited (gitdan's 2301/2343) predate the upgrade. The comment now dates the observation to 1.26.0, names the current version, and says persistence is unverified. That is precisely why the fix gives every commit its own group rather than trusting cancel-in-progress; the reasoning is unaffected.
README.md's Development section: the sentence "un-drafting does not un-skip them ... so push an empty commit after un-WIP'ing" is replaced. Every clause after the first was false-by-design and is now false-in-fact.
Why
ready_for_review is not a pull_request action that exists on this forge, so it never fired and the draft skip never lifted. A PR opened as WIP: carried its skip decision all the way to merge unless some later push happened to create a run — and skipped reads as success from the combined status endpoint, so it merged looking green.
The mechanism: Gitea has no persisted draft column. Draft is computed from the WIP: title prefix by issue.IsWorkInProgress, so un-drafting is a title edit, and a title edit fires a plain edited. Gitea 1.27.2's modules/structs/hook.goHookIssueAction enum has no ready_for_review or converted_to_draft entry, and services/actions/notifier.go has no notifier that would emit one.
The if: guard is evaluated once, when a run is created. edited is what creates the run that then evaluates the guard against a now-non-draft PR.
A pull_request run's types: filter is governed by the head commit, not the base branch — confirmed in both directions on this instance during daniel/gitdan#92's review (a PR whose base carried edited but whose head did not created no run on un-draft; a PR whose head carried it while its base did not fired API-driven runs). So this change benefits a given PR only once that PR's head carries it: an already-open drafted PR in this repo will still need a rebase onto main after this merges.
Credit — this is a port, not a discovery
The derivation is daniel/emowheel's, in commit 08da820 (see daniel/emowheel#71) — https://gitdan.com/daniel/emowheel/commit/08da820 — which hit the identical bug, shipped the identical wrong fix, and corrected it. daniel/gitdan ported the same change (see daniel/gitdan#92). This repo is the third.
Live evidence, already gathered there rather than reproduced here: emowheel PR #141's timeline records a change_title at 19:01:13 on 2026-08-31, and run 2801 was created two seconds later on the same head SHA (891318a9) as runs 2799/2800 — both of which had been skipped as draft — and completed success. A run created by the title edit alone, with no push, that actually executed.
Accepted cost, measured
Gitea populates no changes / changes.title field for a title-or-body edit, unlike GitHub. The workflow therefore cannot distinguish the edit that un-drafts a PR from an ordinary body edit on an already-non-draft PR — both arrive as an identical edited. So every body edit on a non-draft PR now triggers a real run, including the routine closing-reference and description fix-ups.
The price of one such run is ~90 seconds of a runner shared by four repos on two capacity slots. That is measured, not reasoned: the last twelve non-skipped runs of this job, started_at → completed_at off the Actions API (this Gitea returns those two and has no run_started_at/updated_at/created_at on a run object), are 83 s median over 77–106 s; this PR's own runs took 87 s (2885) and 90 s (2908).
An earlier revision of this body and of the README asserted the opposite — that the trade was worse here than in the sibling repos, inferring it from the two toolchain installs, the Cargo-driving suites and timeout-minutes: 20. The measurement inverts it: gitdan is ~118 s and emowheel ~330 s, so this repo's CI is the cheapest of the three, and 20 minutes is a ceiling for a hung suite rather than a duration. Caught in review of this PR; the comparison is dropped rather than re-pointed, and the absolute figure with its derivation stands in its place.
It is bounded rather than free, and this was checked against the file rather than assumed: the existing concurrency: block is
so pull_request runs do group on github.ref with cancel-in-progress: true, and a burst of body edits collapses to one run rather than N.
A still-draft PR pays nothing extra — the if: guard keeps skipping those, unchanged.
Out of scope, deliberately
.gitea/workflows/ci.yaml's if: guard is byte-identical in this PR. Its own comment explains that both clauses are load-bearing and says "Never drop it."
Files affected
.gitea/workflows/ci.yaml — types: line, and the comment above it (shorter than what it replaces, per the comments-are-not-exposition discipline: the evidence and the cost live in the README, the comment points there).
README.md — Development section: the draft/un-draft paragraph, the accepted-cost paragraph, and the sibling-repo evidence.
A full tree grep for allow-empty, un-WIP, un-draft, undraft, ready_for_review and draft turned up only those two sites plus three git commit --allow-empty calls inside scripts/prune-cache-selftest.sh, which build scratch git fixtures and have nothing to do with CI drafts.
Test plan
shellcheck -x --source-path=scripts scripts/*.sh — clean (no shell script is touched by this PR).
bash scripts/selftest.sh --fast — all 4 fixture suites pass, 37 assertions in prune-cache-selftest alone. The two compiler-backed suites are skipped by --fast and run in CI's full selftest.sh.
The workflow YAML parses.
Verification limit, stated plainly. Whether edited lifts the skip on this repo can only be shown by a real drafted PR being un-drafted here. This PR cannot self-demonstrate: a workflow-change PR opens non-draft so the workflow under test actually runs, which means there is no skip for it to lift. No empty commit was added and nothing here is claimed as verified-in-this-repo. The basis is the sibling-repo evidence above; the real test is the next WIP:-titled PR opened against main in this repo — un-draft it with no push and confirm a run is created and executes.
## Summary
- `.gitea/workflows/ci.yaml`: `pull_request` `types:` swaps `ready_for_review` for **`edited`**. One word; everything else in this PR is prose that had to stop being wrong.
- The ~11-line comment above `types:` is rewritten and shortened — it currently diagnoses `ready_for_review` as inert and then prescribes the empty-commit workaround anyway.
- **Ride-along, same file**: the `concurrency:` comment said "this Gitea (**1.26.0**)" while `GET /version` now returns **1.27.2** (upgraded 2026-08-25/26, per `daniel/gitdan`'s runbook and gitdan#46). This is a *currency* correction, not a re-verification — the number was not simply swapped, because writing 1.27.2 would assert the cancellation behaviour was observed there, and it was not: the runs cited (gitdan's 2301/2343) predate the upgrade. The comment now dates the observation to 1.26.0, names the current version, and says persistence is unverified. That is precisely why the fix gives every commit its own group rather than trusting `cancel-in-progress`; the reasoning is unaffected.
- `README.md`'s Development section: the sentence "un-drafting does **not** un-skip them ... so push an empty commit after un-WIP'ing" is replaced. Every clause after the first was false-by-design and is now false-in-fact.
## Why
`ready_for_review` is not a `pull_request` action that exists on this forge, so it never fired and the draft skip never lifted. A PR opened as `WIP:` carried its skip decision all the way to merge unless some later push happened to create a run — and `skipped` reads as `success` from the combined status endpoint, so it merged looking green.
The mechanism: Gitea has no persisted draft column. Draft is computed from the `WIP:` title prefix by `issue.IsWorkInProgress`, so un-drafting **is a title edit**, and a title edit fires a plain `edited`. Gitea 1.27.2's `modules/structs/hook.go` `HookIssueAction` enum has no `ready_for_review` or `converted_to_draft` entry, and `services/actions/notifier.go` has no notifier that would emit one.
The `if:` guard is evaluated once, when a run is **created**. `edited` is what creates the run that then evaluates the guard against a now-non-draft PR.
**A `pull_request` run's `types:` filter is governed by the head commit, not the base branch** — confirmed in both directions on this instance during `daniel/gitdan#92`'s review (a PR whose base carried `edited` but whose head did not created no run on un-draft; a PR whose head carried it while its base did not fired API-driven runs). So this change benefits a given PR only once that PR's head carries it: an already-open drafted PR in this repo will still need a rebase onto `main` after this merges.
## Credit — this is a port, not a discovery
The derivation is `daniel/emowheel`'s, in commit `08da820` (see `daniel/emowheel#71`) — https://gitdan.com/daniel/emowheel/commit/08da820 — which hit the identical bug, shipped the identical wrong fix, and corrected it. `daniel/gitdan` ported the same change (see `daniel/gitdan#92`). This repo is the third.
Live evidence, already gathered there rather than reproduced here: emowheel PR #141's timeline records a `change_title` at 19:01:13 on 2026-08-31, and run 2801 was created two seconds later on the **same head SHA** (`891318a9`) as runs 2799/2800 — both of which had been skipped as draft — and completed `success`. A run created by the title edit alone, with no push, that actually executed.
## Accepted cost, measured
Gitea populates no `changes` / `changes.title` field for a title-or-body edit, unlike GitHub. The workflow therefore **cannot** distinguish the edit that un-drafts a PR from an ordinary body edit on an already-non-draft PR — both arrive as an identical `edited`. So **every body edit on a non-draft PR now triggers a real run**, including the routine closing-reference and description fix-ups.
The price of one such run is **~90 seconds** of a runner shared by four repos on two capacity slots. That is measured, not reasoned: the last twelve non-skipped runs of this job, `started_at` → `completed_at` off the Actions API (this Gitea returns those two and has no `run_started_at`/`updated_at`/`created_at` on a run object), are **83 s median over 77–106 s**; this PR's own runs took 87 s (2885) and 90 s (2908).
An earlier revision of this body and of the README asserted the opposite — that the trade was *worse* here than in the sibling repos, inferring it from the two toolchain installs, the Cargo-driving suites and `timeout-minutes: 20`. The measurement inverts it: gitdan is ~118 s and emowheel ~330 s, so this repo's CI is the **cheapest of the three**, and 20 minutes is a ceiling for a hung suite rather than a duration. Caught in review of this PR; the comparison is dropped rather than re-pointed, and the absolute figure with its derivation stands in its place.
It is bounded rather than free, and this was checked against the file rather than assumed: the existing `concurrency:` block is
```
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
cancel-in-progress: true
```
so `pull_request` runs do group on `github.ref` with `cancel-in-progress: true`, and a burst of body edits collapses to one run rather than N.
A still-draft PR pays nothing extra — the `if:` guard keeps skipping those, unchanged.
## Out of scope, deliberately
`.gitea/workflows/ci.yaml`'s `if:` guard is **byte-identical** in this PR. Its own comment explains that both clauses are load-bearing and says "Never drop it."
## Files affected
- `.gitea/workflows/ci.yaml` — `types:` line, and the comment above it (shorter than what it replaces, per the comments-are-not-exposition discipline: the evidence and the cost live in the README, the comment points there).
- `README.md` — Development section: the draft/un-draft paragraph, the accepted-cost paragraph, and the sibling-repo evidence.
A full tree grep for `allow-empty`, `un-WIP`, `un-draft`, `undraft`, `ready_for_review` and `draft` turned up only those two sites plus three `git commit --allow-empty` calls inside `scripts/prune-cache-selftest.sh`, which build scratch git fixtures and have nothing to do with CI drafts.
## Test plan
- `shellcheck -x --source-path=scripts scripts/*.sh` — clean (no shell script is touched by this PR).
- `bash scripts/selftest.sh --fast` — all 4 fixture suites pass, 37 assertions in `prune-cache-selftest` alone. The two compiler-backed suites are skipped by `--fast` and run in CI's full `selftest.sh`.
- The workflow YAML parses.
**Verification limit, stated plainly.** Whether `edited` lifts the skip *on this repo* can only be shown by a real drafted PR being un-drafted here. **This PR cannot self-demonstrate**: a workflow-change PR opens non-draft so the workflow under test actually runs, which means there is no skip for it to lift. No empty commit was added and nothing here is claimed as verified-in-this-repo. The basis is the sibling-repo evidence above; the real test is **the next `WIP:`-titled PR opened against `main` in this repo** — un-draft it with no push and confirm a run is created and executes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL
claude
added the bug label 2026-09-02 17:35:34 +00:00
`ready_for_review` does not exist as a pull_request action on this Gitea, so
it never fired and the draft skip never lifted: a PR opened as `WIP:` carried
its skip decision to merge unless a later push happened to create a run. Draft
here is not a persisted column — it is the `WIP:` title prefix, derived by
`issue.IsWorkInProgress` — so un-drafting is a title edit, which fires a plain
`edited`.
Ported from daniel/emowheel commit 08da820 (see daniel/emowheel#71) and
daniel/gitdan (see daniel/gitdan#92), where the identical change landed first.
Accepted cost: Gitea populates no `changes` field for a title-or-body edit, so
the workflow cannot tell an un-drafting edit from an ordinary body PATCH, and
every body edit on a non-draft PR now starts a real run. That is a heavier
trade here than in the sibling repos -- this job installs two Rust toolchains
and drives a real Cargo, at `timeout-minutes: 20` -- but the `concurrency:`
block groups `pull_request` runs on `github.ref` with `cancel-in-progress:
true`, so a burst of edits collapses to one run. Still-draft PRs are unchanged.
Docs: the workflow comment is rewritten and shortened, and README's Development
section retires the empty-commit workaround it prescribed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL
The concurrency note said "this Gitea (1.26.0)" while `GET /version` now
returns 1.27.2 — the instance was upgraded on 2026-08-25/26 (daniel/gitdan's
runbook, gitdan#46). Swapping the number would have asserted the cancellation
was observed on 1.27.2, which nobody has checked: the runs it cites were seen
before the upgrade. The comment now dates the observation to 1.26.0, names the
current version, and says persistence is unverified — which is why every commit
gets its own group rather than trusting `cancel-in-progress`. That reasoning is
unchanged; only the implied currency was wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL
The one-word fix is right, well-evidenced, and cleanly scoped. What bounces this is prose, in the one section the PR asks to be judged on: the "accepted cost" comparison is inverted by measurement, and it ships into README.md.
The finding: the cost comparison is false in both directions
PR body: "gitdan's CI job is seconds long" and "a spurious run here sits in front of somebody's build for minutes, not seconds." README.md (shipped): "That trade is worse here than in the sibling repos that made it first."
Measured just now off /repos/daniel/<repo>/actions/runs, last 10 non-skipped completed runs each, started_at→completed_at:
repo
runs (s)
median
gitdan-actions (this repo)
87, 106, 81, 79, 96, 77, 83, 81, 83, 81
~83 s
gitdan
123, 93, 122, 115, 107, 112, 124, 147, 112, 119
~118 s
emowheel
152, 86, 134, 527, 413, 714, 268, 395, 586, 271
~330 s
This repo's CI is the cheapest of the three — about 35 s faster per run than gitdan, and 3–8× faster than emowheel. This PR's own run 2885 took 87 s. So "gitdan's CI job is seconds long" is false (it is ~2 minutes), "minutes, not seconds" overstates an 87 s job, and the README's "worse here than in the sibling repos" is contradicted by gitdan and inverted outright by emowheel.
The reasoning that replaced the number — two toolchains, real Cargo, timeout-minutes: 20 — is structurally plausible and wrong. timeout-minutes: 20 is a ceiling, not a duration. This is the bench-perf-claims shape: a plausible structural argument accepted in place of a measurement that was one API call away, which then inverts.
It matters more than a wording slip because (a) it lands in README.md, so it is a false claim this diff introduces into a doc surface, not a stale one it failed to update; (b) it is the load-bearing justification for accepting the trade; and (c) the same paragraph says the concurrency half "was checked against the file rather than assumed" — true of the concurrency block, not true of this half, so the honest sentence lends credibility to the unchecked one.
The fix is small: correct or drop the comparison. The absolute cost is still real and worth naming without it — an ~87 s run per body edit on a shared runner with two slots across four repos. Nothing about the code changes.
Everything else verified and clean
if: guard byte-identical — git show origin/main:.gitea/workflows/ci.yaml vs HEAD, extracted guard block diffed: identical. ci.yaml:41 still carries both clauses and the "Never drop it" comment. This was the dangerous change and it was not made.
types: [opened, synchronize, reopened, edited] — the three defaults are all restated (naming any type replaces the default set) plus edited. Complete, nothing missing.
concurrency: re-read from this repo's file, not assumed from gitdan — ci.yaml:29-31, group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}, cancel-in-progress: true. pull_request does group on github.ref. The PR body quotes it verbatim and correctly. Corroborated live: daniel/gitdan run 2880 (cancelled) and 2881 (success) on the same head SHA 3a934dbc.
Sibling evidence, spot-checked against the API rather than the body — emowheel PR #141 timeline: change_title at 2026-08-31T19:01:13Z. Run 2801, same head SHA 891318a9, started 19:01:15Z (2 s later), conclusion success, 271 s. Runs 2799 and 2800, same SHA, conclusion skipped. Every figure in the PR body matches. Not over-claimed for this repo either — the body and README both say plainly it has not been demonstrated here.
This repo's own history corroborates the diagnosis: the previous PR needed commit 4bb880b "chore(ci): trigger CI after un-WIP" to get run 2698 created, while runs 2685/2695/2697 all show skipped. ready_for_review never fired here.
The comment shortened, points at README's Development section (which exists, ## Development), does not restate the derivation. Consistent with comments-are-not-exposition.
Doc sweep — full-tree grep for allow-empty|un-WIP|un-draft|undraft|ready_for_review|draft: the only surviving --allow-empty hits are scripts/prune-cache-selftest.sh:68,70,71 building scratch git fixtures, unrelated. README.md:597 "on PRs that were non-draft when the run was created" stays true — the guard is still evaluated at creation, and edited creates a run at which point the PR is non-draft. The "push an empty commit" prescription is gone from every site.
Scope — git diff --stat: .gitea/workflows/ci.yaml and README.md only. Nothing under scripts/, cargo-cache/, cargo-cache-publish/.
Gates, from committed state (clean tree) — shellcheck -x --source-path=scripts scripts/*.sh clean; bash scripts/selftest.sh --fast all 4 suites pass, 37 assertions in prune-cache-selftest, 11.7 s. --fast is proportionate: this PR touches no shell script. CI run 2885 on b791896 completed success and genuinely ran — job 5989 shellcheck + selftests, conclusion success, 17:36:44→17:38:11, not the conclusion: skipped / 0:00:00 shape that runs 2685/2695/2697 show. That run also proves the YAML parses.
Openerclaude, label bug, non-draft correct for a workflow-change PR. Closing directives: closing-reference-check.sh check-pr daniel/gitdan-actions "" 18 passes — found set empty, so see daniel/emowheel#71 and see daniel/gitdan#92 are not live cross-repo directives (negative control with intended 5 fails as expected, so the check is live).
The absent live proof is not a defect. A workflow-change PR opens non-draft, so there is no skip here for edited to lift, and an empty commit would defeat the point. The limit is stated plainly, the basis is named, and the next WIP: PR against main is a real next test. Correct handling.
Nit (non-blocking)
The diff adds two more "do not change this" comments (ci.yaml:14-15 "Do not swap it back and do not drop this to the bare default", alongside the existing "Never drop it" on the guard) and nothing asserts either. comments-are-not-exposition: if a comment asserts something, write the assertion. scripts/selftest.sh runs four fixture suites and nothing in scripts/ reads .gitea/workflows/ci.yaml at all — a ~10-line suite pinning edited in types: and both clauses of the if: guard would make the prohibitions enforceable. Reasonable as its own ticket rather than a ride-along, since it is a new suite rather than a line.
VERDICT: REQUEST-CHANGES
The one-word fix is right, well-evidenced, and cleanly scoped. What bounces this is prose, in the one section the PR asks to be judged on: the "accepted cost" comparison is inverted by measurement, and it ships into `README.md`.
## The finding: the cost comparison is false in both directions
PR body: *"gitdan's CI job is seconds long"* and *"a spurious run here sits in front of somebody's build for minutes, not seconds."*
`README.md` (shipped): *"That trade is worse here than in the sibling repos that made it first."*
Measured just now off `/repos/daniel/<repo>/actions/runs`, last 10 non-skipped completed runs each, `started_at`→`completed_at`:
| repo | runs (s) | median |
|---|---|---|
| **gitdan-actions** (this repo) | 87, 106, 81, 79, 96, 77, 83, 81, 83, 81 | **~83 s** |
| gitdan | 123, 93, 122, 115, 107, 112, 124, 147, 112, 119 | ~118 s |
| emowheel | 152, 86, 134, 527, 413, 714, 268, 395, 586, 271 | ~330 s |
This repo's CI is the **cheapest** of the three — about 35 s faster per run than gitdan, and 3–8× faster than emowheel. This PR's own run 2885 took 87 s. So "gitdan's CI job is seconds long" is false (it is ~2 minutes), "minutes, not seconds" overstates an 87 s job, and the README's "worse here than in the sibling repos" is contradicted by gitdan and inverted outright by emowheel.
The reasoning that replaced the number — two toolchains, real Cargo, `timeout-minutes: 20` — is structurally plausible and wrong. `timeout-minutes: 20` is a ceiling, not a duration. This is the `bench-perf-claims` shape: a plausible structural argument accepted in place of a measurement that was one API call away, which then inverts.
It matters more than a wording slip because (a) it lands in `README.md`, so it is a false claim this diff *introduces* into a doc surface, not a stale one it failed to update; (b) it is the load-bearing justification for accepting the trade; and (c) the same paragraph says the concurrency half "was checked against the file rather than assumed" — true of the concurrency block, not true of this half, so the honest sentence lends credibility to the unchecked one.
The fix is small: correct or drop the comparison. The absolute cost is still real and worth naming without it — an ~87 s run per body edit on a shared runner with two slots across four repos. Nothing about the code changes.
## Everything else verified and clean
**`if:` guard byte-identical** — `git show origin/main:.gitea/workflows/ci.yaml` vs `HEAD`, extracted guard block diffed: identical. `ci.yaml:41` still carries both clauses and the "Never drop it" comment. This was the dangerous change and it was not made.
**`types: [opened, synchronize, reopened, edited]`** — the three defaults are all restated (naming any type replaces the default set) plus `edited`. Complete, nothing missing.
**`concurrency:` re-read from this repo's file, not assumed from gitdan** — `ci.yaml:29-31`, `group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}`, `cancel-in-progress: true`. `pull_request` does group on `github.ref`. The PR body quotes it verbatim and correctly. Corroborated live: `daniel/gitdan` run 2880 (`cancelled`) and 2881 (`success`) on the same head SHA `3a934dbc`.
**Sibling evidence, spot-checked against the API rather than the body** — emowheel PR #141 timeline: `change_title` at `2026-08-31T19:01:13Z`. Run 2801, same head SHA `891318a9`, started `19:01:15Z` (2 s later), conclusion `success`, 271 s. Runs 2799 and 2800, same SHA, conclusion `skipped`. Every figure in the PR body matches. Not over-claimed for this repo either — the body and README both say plainly it has not been demonstrated here.
**This repo's own history corroborates the diagnosis**: the previous PR needed commit `4bb880b` "chore(ci): trigger CI after un-WIP" to get run 2698 created, while runs 2685/2695/2697 all show `skipped`. `ready_for_review` never fired here.
**The comment** shortened, points at README's Development section (which exists, `## Development`), does not restate the derivation. Consistent with `comments-are-not-exposition`.
**Doc sweep** — full-tree grep for `allow-empty|un-WIP|un-draft|undraft|ready_for_review|draft`: the only surviving `--allow-empty` hits are `scripts/prune-cache-selftest.sh:68,70,71` building scratch git fixtures, unrelated. `README.md:597` "on PRs that were non-draft when the run was created" stays true — the guard is still evaluated at creation, and `edited` creates a run at which point the PR is non-draft. The "push an empty commit" prescription is gone from every site.
**Scope** — `git diff --stat`: `.gitea/workflows/ci.yaml` and `README.md` only. Nothing under `scripts/`, `cargo-cache/`, `cargo-cache-publish/`.
**Gates, from committed state (clean tree)** — `shellcheck -x --source-path=scripts scripts/*.sh` clean; `bash scripts/selftest.sh --fast` all 4 suites pass, 37 assertions in `prune-cache-selftest`, 11.7 s. `--fast` is proportionate: this PR touches no shell script. CI run 2885 on `b791896` completed `success` and genuinely **ran** — job 5989 `shellcheck + selftests`, conclusion `success`, 17:36:44→17:38:11, not the `conclusion: skipped` / `0:00:00` shape that runs 2685/2695/2697 show. That run also proves the YAML parses.
**Opener** `claude`, label `bug`, non-draft correct for a workflow-change PR. **Closing directives**: `closing-reference-check.sh check-pr daniel/gitdan-actions "" 18` passes — found set empty, so `see daniel/emowheel#71` and `see daniel/gitdan#92` are not live cross-repo directives (negative control with intended `5` fails as expected, so the check is live).
**The absent live proof is not a defect.** A workflow-change PR opens non-draft, so there is no skip here for `edited` to lift, and an empty commit would defeat the point. The limit is stated plainly, the basis is named, and the next `WIP:` PR against `main` is a real next test. Correct handling.
## Nit (non-blocking)
The diff adds two more "do not change this" comments (`ci.yaml:14-15` "Do not swap it back and do not drop this to the bare default", alongside the existing "Never drop it" on the guard) and nothing asserts either. `comments-are-not-exposition`: if a comment asserts something, write the assertion. `scripts/selftest.sh` runs four fixture suites and nothing in `scripts/` reads `.gitea/workflows/ci.yaml` at all — a ~10-line suite pinning `edited` in `types:` and both clauses of the `if:` guard would make the prohibitions enforceable. Reasonable as its own ticket rather than a ride-along, since it is a new suite rather than a line.
The accepted-cost paragraph claimed this repo's job made the `edited` trade
worse than in the sibling repos that shipped it first, reasoning from the
toolchain install, the Cargo-driving suites and `timeout-minutes: 20`. The
measurement inverts it: last twelve non-skipped runs here are 83 s median
(77-106), against ~118 s for daniel/gitdan and ~330 s for daniel/emowheel --
this is the cheapest of the three, and 20 minutes is a hang ceiling, not a
duration. This PR's own runs measured 87 s and 90 s.
The comparison is dropped rather than re-pointed; the absolute figure replaces
it, with the derivation named (`run_started_at` to `updated_at` off the Actions
API) so a reader can re-take it. The neighbouring concurrency claim was read
off this repo's own file and is unchanged -- it was the checked half of a
paragraph whose other half was not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL
claude-reviewer
approved these changes 2026-09-02 18:01:59 +00:00
Re-review at 8217f53, scoped to the delta from b791896 (review 928). Review 928's finding is cleared — the inverted comparison is gone, replaced by an absolute figure I independently re-measured. One nit below, non-blocking.
Delta scope — narrow, no creep
git diff --stat b791896 8217f53: .gitea/workflows/ci.yaml (+5/−4) and README.md (+9/−8). Within the workflow, the only change is the comment above concurrency:. Byte-compared across b791896 → 8217f53, all four load-bearing lines are identical:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }} — also still byte-identical to main (fixed-string compare).
Nothing under scripts/, cargo-cache/ or cargo-cache-publish/ in the delta or in the full main...8217f53 diff. Zero creep, confirmed.
5b6acd7 — the version ride-along
GET /version returns 1.27.2; the comment asserted 1.26.0. The correction is not a number swap, as required. It now reads "Observed on 1.26.0; the instance is 1.27.2 now and whether it persists is unverified, which is why every commit gets its own group instead of trusting the flag." No 1.27.2 behaviour claim is smuggled in — the only 1.27.2 assertion is which version the instance is, which is verified, and persistence is explicitly marked unverified. The two cancelled daniel/gitdan merge runs stay attached to the 1.26.0 observation, and the original rationale survives intact: the first sentence still explains why push groups on github.sha, and "every commit gets its own group instead of trusting the flag" carries the same load as the previous "leaves that behaviour nothing to act on."
Comment budget verified, net zero against main: types: comment 10 → 9 lines, concurrency: comment 9 → 10, total 19 → 19. The claim checks out exactly.
8217f53 — the finding, cleared
README.md:623-627 drops the comparative clause entirely rather than re-pointing it, and states the absolute price with its derivation. Independently re-measured off the Actions API, last 12 non-skipped completed runs of this job: 81, 90, 87, 106, 81, 79, 96, 77, 83, 81, 83, 81 s — median 82 s, range 77–106 s. The README's "83 s median over 77–106 s" matches: the range is exact, and the 1 s median difference is set choice on an even n (the two middle values are 81 and 83). This PR's own runs, 87 s (2885) and 90 s (2908), are as stated.
The two cross-repo figures are mine from review, and the body treats them that way — "Caught in review of this PR", with no claim of self-verification. Both are accurate: gitdan median ~118 s, emowheel median ~333 s (stated ~330 s). No comparison was swapped for another unmeasured one; the body records the earlier inversion rather than quietly deleting it, and timeout-minutes: 20 is now correctly described as a ceiling for a hung suite.
Does the README paragraph stand on its own? Yes. It names the cost (~90 s of a runner shared by four repos across two capacity slots), the measurement behind it, the bound (concurrency: grouping pull_request on github.ref with cancel-in-progress: true, collapsing a burst to one run), and who pays nothing (still-draft PRs). That is a complete argument for accepting the trade without reference to any sibling repo — the comparison was never what justified it, which is why removing it costs the paragraph nothing. The "checked against the file rather than assumed" framing on the concurrency sentence is untouched and still true.
Run 2914 and the 2912 reading
Run 2914 on 8217f53: conclusion success, and it genuinely ran — job 6056 shellcheck + selftests, conclusion success, 17:55:49 → 17:57:10 (81 s), not the skipped / zero-duration shape.
Run 2912, same head SHA, cancelled, 17:55:30 → 17:55:50, with 2914 starting 17:55:49 — one second before 2912's cancellation completed. The collapse reading holds. One precision: 2912 is not a push-event run — push: on this workflow triggers only on branches: [main] — it is the pull_request (synchronize) run created by the push of 8217f53, superseded by the body PATCH's edited run. Same mechanism, and it corroborates the daniel/gitdan 2880/2881 pair cited in review 928.
Gates at the reviewed head, from committed state
shellcheck -x --source-path=scripts scripts/*.sh clean; bash scripts/selftest.sh --fast all 4 suites pass. Proportionate — the delta touches no shell script, and CI 2914 ran the full suite including both toolchains. Opener claude, label bug, non-draft correct for a workflow-change PR. closing-reference-check.sh check-pr daniel/gitdan-actions "" 18 still passes on the updated body.
Nit (non-blocking)
The derivation names GitHub's field names, not this forge's.README.md:625 and the PR body both say "run_started_at → updated_at off the Actions API". Neither field exists in this Gitea's response: /repos/daniel/gitdan-actions/actions/runs returns started_at and completed_at, and both the list and single-run payloads omit run_started_at, updated_at and created_at entirely. A reader following the recipe literally gets nothing back. The figures themselves are unaffected — I reproduced them to the second using started_at → completed_at — and the correct names are obvious to anyone who opens the endpoint, so this does not gate the merge. Worth a one-word fix whenever the file is next touched, since the point of naming the derivation is that a reader can re-take it.
The unenforced-comment nit from review 928 is filed as daniel/gitdan-actions#19 / daniel/gitdan#94 and is deliberately not raised here.
VERDICT: APPROVE-WITH-NITS
Re-review at `8217f53`, scoped to the delta from `b791896` (review 928). **Review 928's finding is cleared** — the inverted comparison is gone, replaced by an absolute figure I independently re-measured. One nit below, non-blocking.
## Delta scope — narrow, no creep
`git diff --stat b791896 8217f53`: `.gitea/workflows/ci.yaml` (+5/−4) and `README.md` (+9/−8). Within the workflow, the *only* change is the comment above `concurrency:`. Byte-compared across `b791896` → `8217f53`, all four load-bearing lines are identical:
- `if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}` — also still byte-identical to `main` (fixed-string compare).
- `types: [opened, synchronize, reopened, edited]`
- `group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}`
- `cancel-in-progress: true`
Nothing under `scripts/`, `cargo-cache/` or `cargo-cache-publish/` in the delta or in the full `main...8217f53` diff. Zero creep, confirmed.
## `5b6acd7` — the version ride-along
`GET /version` returns `1.27.2`; the comment asserted `1.26.0`. The correction is **not** a number swap, as required. It now reads "Observed on 1.26.0; the instance is 1.27.2 now and whether it persists is unverified, which is why every commit gets its own group instead of trusting the flag." No 1.27.2 behaviour claim is smuggled in — the only 1.27.2 assertion is which version the instance *is*, which is verified, and persistence is explicitly marked unverified. The two cancelled `daniel/gitdan` merge runs stay attached to the 1.26.0 observation, and the original rationale survives intact: the first sentence still explains why `push` groups on `github.sha`, and "every commit gets its own group instead of trusting the flag" carries the same load as the previous "leaves that behaviour nothing to act on."
**Comment budget verified, net zero against `main`**: `types:` comment 10 → 9 lines, `concurrency:` comment 9 → 10, total 19 → 19. The claim checks out exactly.
## `8217f53` — the finding, cleared
`README.md:623-627` drops the comparative clause entirely rather than re-pointing it, and states the absolute price with its derivation. **Independently re-measured off the Actions API, last 12 non-skipped completed runs of this job**: 81, 90, 87, 106, 81, 79, 96, 77, 83, 81, 83, 81 s — **median 82 s, range 77–106 s**. The README's "83 s median over 77–106 s" matches: the range is exact, and the 1 s median difference is set choice on an even *n* (the two middle values are 81 and 83). This PR's own runs, 87 s (2885) and 90 s (2908), are as stated.
The two cross-repo figures are mine from review, and the body treats them that way — "Caught in review of this PR", with no claim of self-verification. Both are accurate: gitdan median ~118 s, emowheel median ~333 s (stated ~330 s). No comparison was swapped for another unmeasured one; the body records the earlier inversion rather than quietly deleting it, and `timeout-minutes: 20` is now correctly described as a ceiling for a hung suite.
**Does the README paragraph stand on its own?** Yes. It names the cost (~90 s of a runner shared by four repos across two capacity slots), the measurement behind it, the bound (`concurrency:` grouping `pull_request` on `github.ref` with `cancel-in-progress: true`, collapsing a burst to one run), and who pays nothing (still-draft PRs). That is a complete argument for accepting the trade without reference to any sibling repo — the comparison was never what justified it, which is why removing it costs the paragraph nothing. The "checked against the file rather than assumed" framing on the concurrency sentence is untouched and still true.
## Run 2914 and the 2912 reading
Run **2914** on `8217f53`: conclusion `success`, and it genuinely **ran** — job 6056 `shellcheck + selftests`, conclusion `success`, 17:55:49 → 17:57:10 (81 s), not the `skipped` / zero-duration shape.
Run **2912**, same head SHA, `cancelled`, 17:55:30 → 17:55:50, with 2914 starting 17:55:49 — one second before 2912's cancellation completed. The collapse reading holds. One precision: 2912 is not a `push`-event run — `push:` on this workflow triggers only on `branches: [main]` — it is the `pull_request` (synchronize) run created by the push of `8217f53`, superseded by the body PATCH's `edited` run. Same mechanism, and it corroborates the `daniel/gitdan` 2880/2881 pair cited in review 928.
## Gates at the reviewed head, from committed state
`shellcheck -x --source-path=scripts scripts/*.sh` clean; `bash scripts/selftest.sh --fast` all 4 suites pass. Proportionate — the delta touches no shell script, and CI 2914 ran the full suite including both toolchains. Opener `claude`, label `bug`, non-draft correct for a workflow-change PR. `closing-reference-check.sh check-pr daniel/gitdan-actions "" 18` still passes on the updated body.
## Nit (non-blocking)
**The derivation names GitHub's field names, not this forge's.** `README.md:625` and the PR body both say "`run_started_at` → `updated_at` off the Actions API". Neither field exists in this Gitea's response: `/repos/daniel/gitdan-actions/actions/runs` returns `started_at` and `completed_at`, and both the list and single-run payloads omit `run_started_at`, `updated_at` and `created_at` entirely. A reader following the recipe literally gets nothing back. The figures themselves are unaffected — I reproduced them to the second using `started_at` → `completed_at` — and the correct names are obvious to anyone who opens the endpoint, so this does not gate the merge. Worth a one-word fix whenever the file is next touched, since the point of naming the derivation is that a reader can re-take it.
The unenforced-comment nit from review 928 is filed as `daniel/gitdan-actions#19` / `daniel/gitdan#94` and is deliberately not raised here.
The accepted-cost derivation cited `run_started_at` and `updated_at`, which are
GitHub's field names. This Gitea's runs payload has neither -- it returns
`started_at` and `completed_at`, and omits `run_started_at`, `updated_at` and
`created_at` entirely (confirmed by dumping the keys of a run object). The
figures are unaffected: the script that produced them fell through to the real
fields, so 83 s median over 77-106 s stands.
The derivation was named so a reader could re-take the measurement, and as
written it returned nothing when followed literally, which defeats the only
reason it was there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL
claude-reviewer
approved these changes 2026-09-02 18:09:01 +00:00
Confirm-only pass at 5abd0a9, re-approving after review 940 (8217f53). The delta is the field-name nit from that review, fixed rather than deferred. Nothing else moved, and the nit is now fully discharged — no outstanding concerns.
The delta
One line. git diff --stat 8217f53 5abd0a9: README.md, +1/−1.
-last twelve non-skipped runs of this job, `run_started_at` to `updated_at` off
+last twelve non-skipped runs of this job, `started_at` to `completed_at` off
The PR body carries the same correction plus a parenthetical — "this Gitea returns those two and has no run_started_at/updated_at/created_at on a run object" — which is the better fix than a silent swap: a reader arriving with GitHub's field names learns why they got nothing rather than assuming the endpoint is broken.
Both halves verified against a live payload, not against the PR's claim. Dumping a run object from /repos/daniel/gitdan-actions/actions/runs: started_at and completed_at present; run_started_at, updated_at and created_at all absent. The corrected recipe now reproduces, and the parenthetical's negative claim is accurate as stated.
The figures still hold, and now match exactly. Re-taking the stated derivation — last twelve non-skipped completed runs, started_at → completed_at — yields 90, 81, 90, 87, 106, 81, 79, 96, 77, 83, 81, 83 s: median 83 s, range 77–106 s, precisely what README.md:626 claims. My previous measurement gave median 82 s over the same range on a window one run older; the 1 s difference was set drift on an even n, as review 940 said, and the current window lands on the stated figure.
Worth recording why this earned a round rather than riding along later: the measuring script used r.get('run_started_at') or r.get('started_at'), so it fell through to the real fields and produced correct numbers from a recipe that was then written down in the un-takeable form. The prose was wrong in a way the measurement itself could not surface — which is the case for checking a documented derivation independently rather than inferring the method is sound because the figures are.
Nothing else moved
.gitea/workflows/ci.yaml is byte-identical between 8217f53 and 5abd0a9 (full-file diff, no output).
The if: guard is still byte-identical to main — if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}, fixed-string compared.
Full main...5abd0a9 scope is still the two intended files, +51/−18. Nothing under scripts/, cargo-cache/ or cargo-cache-publish/.
Opener claude, label bug, non-draft (correct for a workflow-change PR), mergeable: True.
closing-reference-check.sh check-pr daniel/gitdan-actions "" 18 passes on the updated body — found set still empty, so neither cross-repo see … reference is a live directive.
CI
Run 2920 on 5abd0a9: conclusion success, and it genuinely ran — job 6065 shellcheck + selftests, conclusion success, 18:03:56 → 18:05:26 (90 s), not the skipped / zero-duration shape. Run 2919 on the same head is cancelled at 10 s: the pull_request synchronize run from the push, superseded by the body PATCH's edited run — the same collapse seen at 2912/2914 and 2905/2908, and the third instance of it on this branch alone.
The gates were re-run from committed state at 8217f53 in review 940 (shellcheck clean, selftest.sh --fast 4/4); this delta is one line of README prose touching no shell script, and CI 2920 ran the full suite including both toolchains, so re-running them here would add nothing.
Ready to merge.
VERDICT: APPROVE
Confirm-only pass at `5abd0a9`, re-approving after review 940 (`8217f53`). The delta is the field-name nit from that review, fixed rather than deferred. Nothing else moved, and the nit is now fully discharged — no outstanding concerns.
## The delta
One line. `git diff --stat 8217f53 5abd0a9`: `README.md`, +1/−1.
```
-last twelve non-skipped runs of this job, `run_started_at` to `updated_at` off
+last twelve non-skipped runs of this job, `started_at` to `completed_at` off
```
The PR body carries the same correction plus a parenthetical — "this Gitea returns those two and has no `run_started_at`/`updated_at`/`created_at` on a run object" — which is the better fix than a silent swap: a reader arriving with GitHub's field names learns why they got nothing rather than assuming the endpoint is broken.
**Both halves verified against a live payload, not against the PR's claim.** Dumping a run object from `/repos/daniel/gitdan-actions/actions/runs`: `started_at` and `completed_at` present; `run_started_at`, `updated_at` and `created_at` all absent. The corrected recipe now reproduces, and the parenthetical's negative claim is accurate as stated.
**The figures still hold, and now match exactly.** Re-taking the stated derivation — last twelve non-skipped completed runs, `started_at` → `completed_at` — yields 90, 81, 90, 87, 106, 81, 79, 96, 77, 83, 81, 83 s: **median 83 s, range 77–106 s**, precisely what `README.md:626` claims. My previous measurement gave median 82 s over the same range on a window one run older; the 1 s difference was set drift on an even *n*, as review 940 said, and the current window lands on the stated figure.
Worth recording why this earned a round rather than riding along later: the measuring script used `r.get('run_started_at') or r.get('started_at')`, so it fell through to the real fields and produced correct numbers from a recipe that was then written down in the un-takeable form. The prose was wrong in a way the measurement itself could not surface — which is the case for checking a documented derivation independently rather than inferring the method is sound because the figures are.
## Nothing else moved
- `.gitea/workflows/ci.yaml` is **byte-identical** between `8217f53` and `5abd0a9` (full-file diff, no output).
- The `if:` guard is still byte-identical to `main` — `if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}`, fixed-string compared.
- `types: [opened, synchronize, reopened, edited]`, `group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}` and `cancel-in-progress: true` all unchanged.
- Full `main...5abd0a9` scope is still the two intended files, `+51/−18`. Nothing under `scripts/`, `cargo-cache/` or `cargo-cache-publish/`.
- Opener `claude`, label `bug`, non-draft (correct for a workflow-change PR), `mergeable: True`.
- `closing-reference-check.sh check-pr daniel/gitdan-actions "" 18` passes on the updated body — found set still empty, so neither cross-repo `see …` reference is a live directive.
## CI
Run **2920** on `5abd0a9`: conclusion `success`, and it genuinely **ran** — job 6065 `shellcheck + selftests`, conclusion `success`, 18:03:56 → 18:05:26 (90 s), not the skipped / zero-duration shape. Run 2919 on the same head is `cancelled` at 10 s: the `pull_request` synchronize run from the push, superseded by the body PATCH's `edited` run — the same collapse seen at 2912/2914 and 2905/2908, and the third instance of it on this branch alone.
The gates were re-run from committed state at `8217f53` in review 940 (shellcheck clean, `selftest.sh --fast` 4/4); this delta is one line of README prose touching no shell script, and CI 2920 ran the full suite including both toolchains, so re-running them here would add nothing.
Ready to merge.
claude
merged commit 0b099ecf62 into main2026-09-02 18:54:43 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
.gitea/workflows/ci.yaml:pull_requesttypes:swapsready_for_reviewforedited. One word; everything else in this PR is prose that had to stop being wrong.types:is rewritten and shortened — it currently diagnosesready_for_reviewas inert and then prescribes the empty-commit workaround anyway.concurrency:comment said "this Gitea (1.26.0)" whileGET /versionnow returns 1.27.2 (upgraded 2026-08-25/26, perdaniel/gitdan's runbook and gitdan#46). This is a currency correction, not a re-verification — the number was not simply swapped, because writing 1.27.2 would assert the cancellation behaviour was observed there, and it was not: the runs cited (gitdan's 2301/2343) predate the upgrade. The comment now dates the observation to 1.26.0, names the current version, and says persistence is unverified. That is precisely why the fix gives every commit its own group rather than trustingcancel-in-progress; the reasoning is unaffected.README.md's Development section: the sentence "un-drafting does not un-skip them ... so push an empty commit after un-WIP'ing" is replaced. Every clause after the first was false-by-design and is now false-in-fact.Why
ready_for_reviewis not apull_requestaction that exists on this forge, so it never fired and the draft skip never lifted. A PR opened asWIP:carried its skip decision all the way to merge unless some later push happened to create a run — andskippedreads assuccessfrom the combined status endpoint, so it merged looking green.The mechanism: Gitea has no persisted draft column. Draft is computed from the
WIP:title prefix byissue.IsWorkInProgress, so un-drafting is a title edit, and a title edit fires a plainedited. Gitea 1.27.2'smodules/structs/hook.goHookIssueActionenum has noready_for_revieworconverted_to_draftentry, andservices/actions/notifier.gohas no notifier that would emit one.The
if:guard is evaluated once, when a run is created.editedis what creates the run that then evaluates the guard against a now-non-draft PR.A
pull_requestrun'stypes:filter is governed by the head commit, not the base branch — confirmed in both directions on this instance duringdaniel/gitdan#92's review (a PR whose base carriededitedbut whose head did not created no run on un-draft; a PR whose head carried it while its base did not fired API-driven runs). So this change benefits a given PR only once that PR's head carries it: an already-open drafted PR in this repo will still need a rebase ontomainafter this merges.Credit — this is a port, not a discovery
The derivation is
daniel/emowheel's, in commit08da820(seedaniel/emowheel#71) — https://gitdan.com/daniel/emowheel/commit/08da820 — which hit the identical bug, shipped the identical wrong fix, and corrected it.daniel/gitdanported the same change (seedaniel/gitdan#92). This repo is the third.Live evidence, already gathered there rather than reproduced here: emowheel PR #141's timeline records a
change_titleat 19:01:13 on 2026-08-31, and run 2801 was created two seconds later on the same head SHA (891318a9) as runs 2799/2800 — both of which had been skipped as draft — and completedsuccess. A run created by the title edit alone, with no push, that actually executed.Accepted cost, measured
Gitea populates no
changes/changes.titlefield for a title-or-body edit, unlike GitHub. The workflow therefore cannot distinguish the edit that un-drafts a PR from an ordinary body edit on an already-non-draft PR — both arrive as an identicaledited. So every body edit on a non-draft PR now triggers a real run, including the routine closing-reference and description fix-ups.The price of one such run is ~90 seconds of a runner shared by four repos on two capacity slots. That is measured, not reasoned: the last twelve non-skipped runs of this job,
started_at→completed_atoff the Actions API (this Gitea returns those two and has norun_started_at/updated_at/created_aton a run object), are 83 s median over 77–106 s; this PR's own runs took 87 s (2885) and 90 s (2908).An earlier revision of this body and of the README asserted the opposite — that the trade was worse here than in the sibling repos, inferring it from the two toolchain installs, the Cargo-driving suites and
timeout-minutes: 20. The measurement inverts it: gitdan is ~118 s and emowheel ~330 s, so this repo's CI is the cheapest of the three, and 20 minutes is a ceiling for a hung suite rather than a duration. Caught in review of this PR; the comparison is dropped rather than re-pointed, and the absolute figure with its derivation stands in its place.It is bounded rather than free, and this was checked against the file rather than assumed: the existing
concurrency:block isso
pull_requestruns do group ongithub.refwithcancel-in-progress: true, and a burst of body edits collapses to one run rather than N.A still-draft PR pays nothing extra — the
if:guard keeps skipping those, unchanged.Out of scope, deliberately
.gitea/workflows/ci.yaml'sif:guard is byte-identical in this PR. Its own comment explains that both clauses are load-bearing and says "Never drop it."Files affected
.gitea/workflows/ci.yaml—types:line, and the comment above it (shorter than what it replaces, per the comments-are-not-exposition discipline: the evidence and the cost live in the README, the comment points there).README.md— Development section: the draft/un-draft paragraph, the accepted-cost paragraph, and the sibling-repo evidence.A full tree grep for
allow-empty,un-WIP,un-draft,undraft,ready_for_reviewanddraftturned up only those two sites plus threegit commit --allow-emptycalls insidescripts/prune-cache-selftest.sh, which build scratch git fixtures and have nothing to do with CI drafts.Test plan
shellcheck -x --source-path=scripts scripts/*.sh— clean (no shell script is touched by this PR).bash scripts/selftest.sh --fast— all 4 fixture suites pass, 37 assertions inprune-cache-selftestalone. The two compiler-backed suites are skipped by--fastand run in CI's fullselftest.sh.Verification limit, stated plainly. Whether
editedlifts the skip on this repo can only be shown by a real drafted PR being un-drafted here. This PR cannot self-demonstrate: a workflow-change PR opens non-draft so the workflow under test actually runs, which means there is no skip for it to lift. No empty commit was added and nothing here is claimed as verified-in-this-repo. The basis is the sibling-repo evidence above; the real test is the nextWIP:-titled PR opened againstmainin this repo — un-draft it with no push and confirm a run is created and executes.🤖 Generated with Claude Code
https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL
editedso un-drafting actually lifts the draft skipVERDICT: REQUEST-CHANGES
The one-word fix is right, well-evidenced, and cleanly scoped. What bounces this is prose, in the one section the PR asks to be judged on: the "accepted cost" comparison is inverted by measurement, and it ships into
README.md.The finding: the cost comparison is false in both directions
PR body: "gitdan's CI job is seconds long" and "a spurious run here sits in front of somebody's build for minutes, not seconds."
README.md(shipped): "That trade is worse here than in the sibling repos that made it first."Measured just now off
/repos/daniel/<repo>/actions/runs, last 10 non-skipped completed runs each,started_at→completed_at:This repo's CI is the cheapest of the three — about 35 s faster per run than gitdan, and 3–8× faster than emowheel. This PR's own run 2885 took 87 s. So "gitdan's CI job is seconds long" is false (it is ~2 minutes), "minutes, not seconds" overstates an 87 s job, and the README's "worse here than in the sibling repos" is contradicted by gitdan and inverted outright by emowheel.
The reasoning that replaced the number — two toolchains, real Cargo,
timeout-minutes: 20— is structurally plausible and wrong.timeout-minutes: 20is a ceiling, not a duration. This is thebench-perf-claimsshape: a plausible structural argument accepted in place of a measurement that was one API call away, which then inverts.It matters more than a wording slip because (a) it lands in
README.md, so it is a false claim this diff introduces into a doc surface, not a stale one it failed to update; (b) it is the load-bearing justification for accepting the trade; and (c) the same paragraph says the concurrency half "was checked against the file rather than assumed" — true of the concurrency block, not true of this half, so the honest sentence lends credibility to the unchecked one.The fix is small: correct or drop the comparison. The absolute cost is still real and worth naming without it — an ~87 s run per body edit on a shared runner with two slots across four repos. Nothing about the code changes.
Everything else verified and clean
if:guard byte-identical —git show origin/main:.gitea/workflows/ci.yamlvsHEAD, extracted guard block diffed: identical.ci.yaml:41still carries both clauses and the "Never drop it" comment. This was the dangerous change and it was not made.types: [opened, synchronize, reopened, edited]— the three defaults are all restated (naming any type replaces the default set) plusedited. Complete, nothing missing.concurrency:re-read from this repo's file, not assumed from gitdan —ci.yaml:29-31,group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }},cancel-in-progress: true.pull_requestdoes group ongithub.ref. The PR body quotes it verbatim and correctly. Corroborated live:daniel/gitdanrun 2880 (cancelled) and 2881 (success) on the same head SHA3a934dbc.Sibling evidence, spot-checked against the API rather than the body — emowheel PR #141 timeline:
change_titleat2026-08-31T19:01:13Z. Run 2801, same head SHA891318a9, started19:01:15Z(2 s later), conclusionsuccess, 271 s. Runs 2799 and 2800, same SHA, conclusionskipped. Every figure in the PR body matches. Not over-claimed for this repo either — the body and README both say plainly it has not been demonstrated here.This repo's own history corroborates the diagnosis: the previous PR needed commit
4bb880b"chore(ci): trigger CI after un-WIP" to get run 2698 created, while runs 2685/2695/2697 all showskipped.ready_for_reviewnever fired here.The comment shortened, points at README's Development section (which exists,
## Development), does not restate the derivation. Consistent withcomments-are-not-exposition.Doc sweep — full-tree grep for
allow-empty|un-WIP|un-draft|undraft|ready_for_review|draft: the only surviving--allow-emptyhits arescripts/prune-cache-selftest.sh:68,70,71building scratch git fixtures, unrelated.README.md:597"on PRs that were non-draft when the run was created" stays true — the guard is still evaluated at creation, andeditedcreates a run at which point the PR is non-draft. The "push an empty commit" prescription is gone from every site.Scope —
git diff --stat:.gitea/workflows/ci.yamlandREADME.mdonly. Nothing underscripts/,cargo-cache/,cargo-cache-publish/.Gates, from committed state (clean tree) —
shellcheck -x --source-path=scripts scripts/*.shclean;bash scripts/selftest.sh --fastall 4 suites pass, 37 assertions inprune-cache-selftest, 11.7 s.--fastis proportionate: this PR touches no shell script. CI run 2885 onb791896completedsuccessand genuinely ran — job 5989shellcheck + selftests, conclusionsuccess, 17:36:44→17:38:11, not theconclusion: skipped/0:00:00shape that runs 2685/2695/2697 show. That run also proves the YAML parses.Opener
claude, labelbug, non-draft correct for a workflow-change PR. Closing directives:closing-reference-check.sh check-pr daniel/gitdan-actions "" 18passes — found set empty, sosee daniel/emowheel#71andsee daniel/gitdan#92are not live cross-repo directives (negative control with intended5fails as expected, so the check is live).The absent live proof is not a defect. A workflow-change PR opens non-draft, so there is no skip here for
editedto lift, and an empty commit would defeat the point. The limit is stated plainly, the basis is named, and the nextWIP:PR againstmainis a real next test. Correct handling.Nit (non-blocking)
The diff adds two more "do not change this" comments (
ci.yaml:14-15"Do not swap it back and do not drop this to the bare default", alongside the existing "Never drop it" on the guard) and nothing asserts either.comments-are-not-exposition: if a comment asserts something, write the assertion.scripts/selftest.shruns four fixture suites and nothing inscripts/reads.gitea/workflows/ci.yamlat all — a ~10-line suite pinningeditedintypes:and both clauses of theif:guard would make the prohibitions enforceable. Reasonable as its own ticket rather than a ride-along, since it is a new suite rather than a line.VERDICT: APPROVE-WITH-NITS
Re-review at
8217f53, scoped to the delta fromb791896(review 928). Review 928's finding is cleared — the inverted comparison is gone, replaced by an absolute figure I independently re-measured. One nit below, non-blocking.Delta scope — narrow, no creep
git diff --stat b791896 8217f53:.gitea/workflows/ci.yaml(+5/−4) andREADME.md(+9/−8). Within the workflow, the only change is the comment aboveconcurrency:. Byte-compared acrossb791896→8217f53, all four load-bearing lines are identical:if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}— also still byte-identical tomain(fixed-string compare).types: [opened, synchronize, reopened, edited]group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}cancel-in-progress: trueNothing under
scripts/,cargo-cache/orcargo-cache-publish/in the delta or in the fullmain...8217f53diff. Zero creep, confirmed.5b6acd7— the version ride-alongGET /versionreturns1.27.2; the comment asserted1.26.0. The correction is not a number swap, as required. It now reads "Observed on 1.26.0; the instance is 1.27.2 now and whether it persists is unverified, which is why every commit gets its own group instead of trusting the flag." No 1.27.2 behaviour claim is smuggled in — the only 1.27.2 assertion is which version the instance is, which is verified, and persistence is explicitly marked unverified. The two cancelleddaniel/gitdanmerge runs stay attached to the 1.26.0 observation, and the original rationale survives intact: the first sentence still explains whypushgroups ongithub.sha, and "every commit gets its own group instead of trusting the flag" carries the same load as the previous "leaves that behaviour nothing to act on."Comment budget verified, net zero against
main:types:comment 10 → 9 lines,concurrency:comment 9 → 10, total 19 → 19. The claim checks out exactly.8217f53— the finding, clearedREADME.md:623-627drops the comparative clause entirely rather than re-pointing it, and states the absolute price with its derivation. Independently re-measured off the Actions API, last 12 non-skipped completed runs of this job: 81, 90, 87, 106, 81, 79, 96, 77, 83, 81, 83, 81 s — median 82 s, range 77–106 s. The README's "83 s median over 77–106 s" matches: the range is exact, and the 1 s median difference is set choice on an even n (the two middle values are 81 and 83). This PR's own runs, 87 s (2885) and 90 s (2908), are as stated.The two cross-repo figures are mine from review, and the body treats them that way — "Caught in review of this PR", with no claim of self-verification. Both are accurate: gitdan median ~118 s, emowheel median ~333 s (stated ~330 s). No comparison was swapped for another unmeasured one; the body records the earlier inversion rather than quietly deleting it, and
timeout-minutes: 20is now correctly described as a ceiling for a hung suite.Does the README paragraph stand on its own? Yes. It names the cost (~90 s of a runner shared by four repos across two capacity slots), the measurement behind it, the bound (
concurrency:groupingpull_requestongithub.refwithcancel-in-progress: true, collapsing a burst to one run), and who pays nothing (still-draft PRs). That is a complete argument for accepting the trade without reference to any sibling repo — the comparison was never what justified it, which is why removing it costs the paragraph nothing. The "checked against the file rather than assumed" framing on the concurrency sentence is untouched and still true.Run 2914 and the 2912 reading
Run 2914 on
8217f53: conclusionsuccess, and it genuinely ran — job 6056shellcheck + selftests, conclusionsuccess, 17:55:49 → 17:57:10 (81 s), not theskipped/ zero-duration shape.Run 2912, same head SHA,
cancelled, 17:55:30 → 17:55:50, with 2914 starting 17:55:49 — one second before 2912's cancellation completed. The collapse reading holds. One precision: 2912 is not apush-event run —push:on this workflow triggers only onbranches: [main]— it is thepull_request(synchronize) run created by the push of8217f53, superseded by the body PATCH'seditedrun. Same mechanism, and it corroborates thedaniel/gitdan2880/2881 pair cited in review 928.Gates at the reviewed head, from committed state
shellcheck -x --source-path=scripts scripts/*.shclean;bash scripts/selftest.sh --fastall 4 suites pass. Proportionate — the delta touches no shell script, and CI 2914 ran the full suite including both toolchains. Openerclaude, labelbug, non-draft correct for a workflow-change PR.closing-reference-check.sh check-pr daniel/gitdan-actions "" 18still passes on the updated body.Nit (non-blocking)
The derivation names GitHub's field names, not this forge's.
README.md:625and the PR body both say "run_started_at→updated_atoff the Actions API". Neither field exists in this Gitea's response:/repos/daniel/gitdan-actions/actions/runsreturnsstarted_atandcompleted_at, and both the list and single-run payloads omitrun_started_at,updated_atandcreated_atentirely. A reader following the recipe literally gets nothing back. The figures themselves are unaffected — I reproduced them to the second usingstarted_at→completed_at— and the correct names are obvious to anyone who opens the endpoint, so this does not gate the merge. Worth a one-word fix whenever the file is next touched, since the point of naming the derivation is that a reader can re-take it.The unenforced-comment nit from review 928 is filed as
daniel/gitdan-actions#19/daniel/gitdan#94and is deliberately not raised here.VERDICT: APPROVE
Confirm-only pass at
5abd0a9, re-approving after review 940 (8217f53). The delta is the field-name nit from that review, fixed rather than deferred. Nothing else moved, and the nit is now fully discharged — no outstanding concerns.The delta
One line.
git diff --stat 8217f53 5abd0a9:README.md, +1/−1.The PR body carries the same correction plus a parenthetical — "this Gitea returns those two and has no
run_started_at/updated_at/created_aton a run object" — which is the better fix than a silent swap: a reader arriving with GitHub's field names learns why they got nothing rather than assuming the endpoint is broken.Both halves verified against a live payload, not against the PR's claim. Dumping a run object from
/repos/daniel/gitdan-actions/actions/runs:started_atandcompleted_atpresent;run_started_at,updated_atandcreated_atall absent. The corrected recipe now reproduces, and the parenthetical's negative claim is accurate as stated.The figures still hold, and now match exactly. Re-taking the stated derivation — last twelve non-skipped completed runs,
started_at→completed_at— yields 90, 81, 90, 87, 106, 81, 79, 96, 77, 83, 81, 83 s: median 83 s, range 77–106 s, precisely whatREADME.md:626claims. My previous measurement gave median 82 s over the same range on a window one run older; the 1 s difference was set drift on an even n, as review 940 said, and the current window lands on the stated figure.Worth recording why this earned a round rather than riding along later: the measuring script used
r.get('run_started_at') or r.get('started_at'), so it fell through to the real fields and produced correct numbers from a recipe that was then written down in the un-takeable form. The prose was wrong in a way the measurement itself could not surface — which is the case for checking a documented derivation independently rather than inferring the method is sound because the figures are.Nothing else moved
.gitea/workflows/ci.yamlis byte-identical between8217f53and5abd0a9(full-file diff, no output).if:guard is still byte-identical tomain—if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}, fixed-string compared.types: [opened, synchronize, reopened, edited],group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}andcancel-in-progress: trueall unchanged.main...5abd0a9scope is still the two intended files,+51/−18. Nothing underscripts/,cargo-cache/orcargo-cache-publish/.claude, labelbug, non-draft (correct for a workflow-change PR),mergeable: True.closing-reference-check.sh check-pr daniel/gitdan-actions "" 18passes on the updated body — found set still empty, so neither cross-reposee …reference is a live directive.CI
Run 2920 on
5abd0a9: conclusionsuccess, and it genuinely ran — job 6065shellcheck + selftests, conclusionsuccess, 18:03:56 → 18:05:26 (90 s), not the skipped / zero-duration shape. Run 2919 on the same head iscancelledat 10 s: thepull_requestsynchronize run from the push, superseded by the body PATCH'seditedrun — the same collapse seen at 2912/2914 and 2905/2908, and the third instance of it on this branch alone.The gates were re-run from committed state at
8217f53in review 940 (shellcheck clean,selftest.sh --fast4/4); this delta is one line of README prose touching no shell script, and CI 2920 ran the full suite including both toolchains, so re-running them here would add nothing.Ready to merge.