From b791896e032a7df960702d3f55bbf94d9e28098f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 12:34:24 -0500 Subject: [PATCH 1/4] fix(ci): trigger on `edited` so un-drafting actually lifts the draft skip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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) Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL --- .gitea/workflows/ci.yaml | 19 +++++++++---------- README.md | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 6315801..cc60b6e 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -5,17 +5,16 @@ on: branches: [main] pull_request: branches: [main] - # Spelled out only to keep `ready_for_review` in the list — naming any type - # replaces the whole default set, so the other three have to be restated. - # It is inert on this instance (draft state here is the `WIP:` title - # prefix, so un-drafting is a title edit and raises no - # `ready_for_review` action) and costs nothing. + # Spelled out only to keep `edited` in the list — naming any type replaces + # the whole default set, so the other three have to be restated. # - # The consequence, which is the part that bites: the `if:` guard below is - # evaluated when a run is CREATED, and un-drafting creates no run. A PR - # opened as a draft keeps its skip decision until something else produces - # one. Push an empty commit after un-WIP'ing. - types: [opened, synchronize, reopened, ready_for_review] + # `edited`, not `ready_for_review`: draft here is the `WIP:` title prefix, + # so un-drafting is a title edit and no ready-for-review action is ever + # raised. That edit is what creates the run which lifts the `if:` skip + # below (decided once, when a run is CREATED). Do not swap it back and do + # not drop this to the bare default — either restores the bug. The accepted + # cost and the evidence are in README's Development section. + types: [opened, synchronize, reopened, edited] # gitdan-ci runs four repos' CI on two capacity slots, and the compiler-backed # suites below are multi-minute. A superseded run costs a slot in front of diff --git a/README.md b/README.md index db8dbff..bf6f5dd 100644 --- a/README.md +++ b/README.md @@ -605,10 +605,42 @@ cannot measure) and references no credentials; the scratch workspaces the compiler-backed suites build use path dependencies only, so nothing reaches crates.io. It runs the full suite rather than `--fast`, because the two compiler-backed suites are the ones that check this scheme -against real Cargo instead of against a fixture. Draft (`WIP:`-titled) PRs -skip it, and un-drafting does **not** un-skip them — the guard is evaluated -when a run is created and un-drafting creates none, so push an empty commit -after un-WIP'ing. +against real Cargo instead of against a fixture. + +**Draft (`WIP:`-titled) PRs skip it; un-drafting un-skips them, through +`edited`** — no empty commit needed. The skip is decided when a run is +*created*, so lifting it needs an event that creates one, and un-drafting on +this Gitea is a title edit: `edited` is in the workflow's `pull_request` types +for exactly that reason. `ready_for_review` held that slot first and never +fired — this Gitea has no draft column and no ready-for-review event at all, +draft being computed from the title prefix — so a PR opened as `WIP:` carried +its skip decision all the way to merge unless some later push happened to +create a run. Do not swap the type back and do not drop the `types:` list to +its bare default; either restores the bug. + +**Accepted cost: a body edit on an already-non-draft PR now triggers a real +run.** Gitea populates no `changes` field for a title-or-body edit, unlike +GitHub, so the workflow cannot tell the edit that un-drafts a PR from an +ordinary body PATCH — a closing-reference fix-up, say. That trade is worse +here than in the sibling repos that made it first: this job installs two Rust +toolchains and runs suites that drive a real Cargo against real scratch +workspaces, with `timeout-minutes: 20`, on a runner shared across four repos +on two capacity slots. It is bounded, though: the workflow's `concurrency:` +block groups `pull_request` runs on `github.ref` with `cancel-in-progress: +true`, so a burst of edits collapses to one run rather than N. A still-draft +PR pays nothing extra — the `if:` guard skips those exactly as before. + +The evidence for `edited` comes from `daniel/emowheel`, which hit the identical +bug, shipped the identical wrong fix, and corrected it in commit `08da820` (see +`daniel/emowheel#71`): Gitea 1.27.2's `HookIssueAction` enum has no +`ready_for_review` entry and no notifier emits one, while +`issue.IsWorkInProgress` derives draft from the title. Live since: emowheel PR +#141 was un-drafted at 19:01:13 on 2026-08-31 and run 2801 was created two +seconds later on the **same** head SHA as the two runs skipped before it — a +run created by the title edit alone, with no push, that executed and passed. +`daniel/gitdan` ported the same one-word change. That it behaves the same way +*here* has not been demonstrated in this repo; the next `WIP:` PR opened +against `main` is the test. This repo is consumed by three other repos' CI at `@v1`, a moving tag, so a change here reaches all of them at once. That is what the gate is for. -- 2.43.0 From 5b6acd7b3b167f0a1b94d5baafc6fd7abf2d77d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 12:49:56 -0500 Subject: [PATCH 2/4] docs(ci): date the cancellation observation to 1.26.0, not the current version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL --- .gitea/workflows/ci.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index cc60b6e..ac594f0 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -21,10 +21,11 @@ on: # somebody's build, so drop it. # # `push` groups on `github.sha` rather than `github.ref`: a constant per-branch -# group is what let this Gitea (1.26.0) cancel two of daniel/gitdan's merge -# runs outright while `cancel-in-progress` was gated away from `push` entirely -# — see the long note in that repo's ci.yaml for the evidence. Giving every -# commit its own group leaves that behaviour nothing to act on. +# group is what let this Gitea cancel two of daniel/gitdan's merge runs +# outright while `cancel-in-progress` was gated away from `push` entirely — +# see the long note in that repo's ci.yaml. 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. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }} cancel-in-progress: true -- 2.43.0 From 8217f53d4a9ac03e55b28299125edf7a31392d04 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 12:55:24 -0500 Subject: [PATCH 3/4] docs(ci): measure the accepted cost instead of comparing it unmeasured 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) Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bf6f5dd..3c61560 100644 --- a/README.md +++ b/README.md @@ -621,14 +621,15 @@ its bare default; either restores the bug. **Accepted cost: a body edit on an already-non-draft PR now triggers a real run.** Gitea populates no `changes` field for a title-or-body edit, unlike GitHub, so the workflow cannot tell the edit that un-drafts a PR from an -ordinary body PATCH — a closing-reference fix-up, say. That trade is worse -here than in the sibling repos that made it first: this job installs two Rust -toolchains and runs suites that drive a real Cargo against real scratch -workspaces, with `timeout-minutes: 20`, on a runner shared across four repos -on two capacity slots. It is bounded, though: the workflow's `concurrency:` -block groups `pull_request` runs on `github.ref` with `cancel-in-progress: -true`, so a burst of edits collapses to one run rather than N. A still-draft -PR pays nothing extra — the `if:` guard skips those exactly as before. +ordinary body PATCH — a closing-reference fix-up, say. The price is around +**90 seconds** of a runner shared across four repos on two capacity slots: the +last twelve non-skipped runs of this job, `run_started_at` to `updated_at` off +the Actions API, are 83 s median over 77–106 s. `timeout-minutes: 20` is a +ceiling for a hung suite, not a duration. It is also bounded: the workflow's +`concurrency:` block groups `pull_request` runs on `github.ref` with +`cancel-in-progress: true`, so a burst of edits collapses to one run rather +than N. A still-draft PR pays nothing extra — the `if:` guard skips those +exactly as before. The evidence for `edited` comes from `daniel/emowheel`, which hit the identical bug, shipped the identical wrong fix, and corrected it in commit `08da820` (see -- 2.43.0 From 5abd0a9968b8d213f3fce68ebceea9d65bba9a04 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 13:03:45 -0500 Subject: [PATCH 4/4] docs(ci): name the timing fields this forge actually returns 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) Claude-Session: https://claude.ai/code/session_01LjbhSqQf3pwnPA6MVaWcWL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c61560..273fea4 100644 --- a/README.md +++ b/README.md @@ -623,7 +623,7 @@ run.** Gitea populates no `changes` field for a title-or-body edit, unlike GitHub, so the workflow cannot tell the edit that un-drafts a PR from an ordinary body PATCH — a closing-reference fix-up, say. The price is around **90 seconds** of a runner shared across four repos on two capacity slots: the -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 Actions API, are 83 s median over 77–106 s. `timeout-minutes: 20` is a ceiling for a hung suite, not a duration. It is also bounded: the workflow's `concurrency:` block groups `pull_request` runs on `github.ref` with -- 2.43.0