From ef29a1271e21855816cd404219616558cf26e061 Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Thu, 8 Oct 2026 03:36:02 +0000 Subject: [PATCH 1/5] CI: a newer run cancels the one it supersedes Workflow-level concurrency, per PR or branch, with cancel-in-progress. The earlier attempt (job-level, on the e2e job) didn't cancel anything; Gitea documents the workflow-level form. A label other than e2e:full starts a run whose jobs all skip, so it gets a group of its own and can't cancel the real run. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE --- .gitea/workflows/ci.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7379336..e7ec349 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -18,6 +18,17 @@ on: # Manual re-run, e.g. after rotating a credential or raising a budget cap. workflow_dispatch: +# A newer run for the same PR or branch cancels the one it supersedes: there +# is one runner, so a stale run would hold it (and the GPU) to the end. Adding +# a label other than e2e:full starts a run whose jobs all skip; it gets a +# group of its own, so it can't cancel the real one. +concurrency: + group: >- + ${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ + (github.event.action == 'labeled' && github.event.label.name != 'e2e:full') + && format('-label-{0}', github.run_id) || '' }} + cancel-in-progress: true + env: # Full on e2e:full, and outside PRs. FULL: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'e2e:full') }} @@ -76,10 +87,9 @@ jobs: # services, every role on a local model, so it spends nothing but keeps # the RTX 4090 busy: ~10 minutes for the smoke tests, ~25+ for all. On # PRs and manual runs, and only after the checks pass: a PR that's red - # already doesn't spend the GPU. There is one runner and Gitea doesn't - # honour cancel-in-progress, so a run a newer push supersedes holds the - # runner until it's cancelled by hand. Locally, run only what you're - # writing (-k) or diagnosing (--lf). + # already doesn't spend the GPU. A newer push cancels a run it supersedes + # (concurrency, above). Locally, run only what you're writing (-k) or + # diagnosing (--lf). if: >- github.event_name != 'push' && (github.event.action != 'labeled' || github.event.label.name == 'e2e:full') -- 2.54.0 From 9c181d973a2b65ae287220de12a8ebb970c1d44c Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Thu, 8 Oct 2026 03:36:19 +0000 Subject: [PATCH 2/5] Development Plan: superseded runs are cancelled by CI now Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE --- docs/Development-Plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index 5642448..0d7b692 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -81,8 +81,8 @@ Only `nisse-dev` has real code; `nisse-core` and `nisse-ops` are placeholders. runs that full set before its image is built. The end-to-end tests drive the real agent, every role on a local model (so they cost nothing per call), in a repository of the run's own, with a scripted operator; CI runs them after the - checks pass. There is one runner, and a run a newer push - supersedes has to be cancelled by hand. Locally only the tests being written + checks pass. There is one runner, so a newer run for a + PR or branch cancels the one it supersedes (#126). Locally only the tests being written (`-k`) or diagnosed (`--lf`); `scripts/e2e.sh` reports what a run spent. Manual live runs are for the new loop's first runs and phase ends. Findings are filed as issues and either fixed before moving on or deliberately deferred. -- 2.54.0 From 8bfc1e5f24edd397f87a4c444cecb74deac654ae Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Thu, 8 Oct 2026 03:38:05 +0000 Subject: [PATCH 3/5] CI: Gitea's label events are label_updated in the payload Temporarily prints the event, to check what Gitea sends. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE --- .gitea/workflows/ci.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index e7ec349..b125da7 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -22,10 +22,15 @@ on: # is one runner, so a stale run would hold it (and the GPU) to the end. Adding # a label other than e2e:full starts a run whose jobs all skip; it gets a # group of its own, so it can't cancel the real one. +# +# Gitea triggers on `labeled` but hands the workflow its own name for the +# action, `label_updated` (and `synchronized` for a push), so each check +# below accepts both. concurrency: group: >- ${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ - (github.event.action == 'labeled' && github.event.label.name != 'e2e:full') + ((github.event.action == 'labeled' || github.event.action == 'label_updated') + && github.event.label.name != 'e2e:full') && format('-label-{0}', github.run_id) || '' }} cancel-in-progress: true @@ -34,8 +39,17 @@ env: FULL: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'e2e:full') }} jobs: + event: # TEMPORARY, for #126: what Gitea puts in the event + runs-on: ubuntu-latest + steps: + - run: | + echo "action=[${{ github.event.action }}] label=[${{ github.event.label.name }}] event=${{ github.event_name }}" + echo "group=[${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ ((github.event.action == 'labeled' || github.event.action == 'label_updated') && github.event.label.name != 'e2e:full') && format('-label-{0}', github.run_id) || '' }}]" + checks: - if: github.event.action != 'labeled' || github.event.label.name == 'e2e:full' + if: >- + (github.event.action != 'labeled' && github.event.action != 'label_updated') + || github.event.label.name == 'e2e:full' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -92,7 +106,8 @@ jobs: # diagnosing (--lf). if: >- github.event_name != 'push' - && (github.event.action != 'labeled' || github.event.label.name == 'e2e:full') + && ((github.event.action != 'labeled' && github.event.action != 'label_updated') + || github.event.label.name == 'e2e:full') needs: checks runs-on: ubuntu-latest timeout-minutes: 90 -- 2.54.0 From b80225d821f825192558a17d03f7f803387fb96e Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Thu, 8 Oct 2026 03:40:36 +0000 Subject: [PATCH 4/5] CI: tell e2e:full label events by the PR's labels Gitea leaves github.event.label empty. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE --- .gitea/workflows/ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index b125da7..0cd4c22 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -25,12 +25,14 @@ on: # # Gitea triggers on `labeled` but hands the workflow its own name for the # action, `label_updated` (and `synchronized` for a push), so each check -# below accepts both. +# below accepts both. Nor does it say which label was added +# (`github.event.label` is empty), so a label event counts when the PR now +# has e2e:full; adding another label to an e2e:full PR restarts its run. concurrency: group: >- ${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ ((github.event.action == 'labeled' || github.event.action == 'label_updated') - && github.event.label.name != 'e2e:full') + && !contains(github.event.pull_request.labels.*.name, 'e2e:full')) && format('-label-{0}', github.run_id) || '' }} cancel-in-progress: true @@ -44,12 +46,12 @@ jobs: steps: - run: | echo "action=[${{ github.event.action }}] label=[${{ github.event.label.name }}] event=${{ github.event_name }}" - echo "group=[${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ ((github.event.action == 'labeled' || github.event.action == 'label_updated') && github.event.label.name != 'e2e:full') && format('-label-{0}', github.run_id) || '' }}]" + echo "group=[${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ ((github.event.action == 'labeled' || github.event.action == 'label_updated') && !contains(github.event.pull_request.labels.*.name, 'e2e:full')) && format('-label-{0}', github.run_id) || '' }}]" checks: if: >- (github.event.action != 'labeled' && github.event.action != 'label_updated') - || github.event.label.name == 'e2e:full' + || contains(github.event.pull_request.labels.*.name, 'e2e:full') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -107,7 +109,7 @@ jobs: if: >- github.event_name != 'push' && ((github.event.action != 'labeled' && github.event.action != 'label_updated') - || github.event.label.name == 'e2e:full') + || contains(github.event.pull_request.labels.*.name, 'e2e:full')) needs: checks runs-on: ubuntu-latest timeout-minutes: 90 -- 2.54.0 From 8840facceb65891a992f25c1837527c87b5fe464 Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Thu, 8 Oct 2026 03:41:17 +0000 Subject: [PATCH 5/5] CI: drop the temporary event job Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE --- .gitea/workflows/ci.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 0cd4c22..dc2e77e 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -41,13 +41,6 @@ env: FULL: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'e2e:full') }} jobs: - event: # TEMPORARY, for #126: what Gitea puts in the event - runs-on: ubuntu-latest - steps: - - run: | - echo "action=[${{ github.event.action }}] label=[${{ github.event.label.name }}] event=${{ github.event_name }}" - echo "group=[${{ github.workflow }}-${{ github.head_ref || github.ref }}${{ ((github.event.action == 'labeled' || github.event.action == 'label_updated') && !contains(github.event.pull_request.labels.*.name, 'e2e:full')) && format('-label-{0}', github.run_id) || '' }}]" - checks: if: >- (github.event.action != 'labeled' && github.event.action != 'label_updated') -- 2.54.0