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') 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. 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 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 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')