From 3eec49dfd3aa6e9906b009c1e30537a0803ebb78 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 08:52:08 +0200 Subject: [PATCH 1/8] Split wheel install from test execution Separate wheel installation from the headless GUI test step in the release workflow. The wheel is installed and a temporary test directory is created first, then pytest runs from that directory via the action's `working-directory` and `shell` settings so the shipped wheel tests run against the installed package instead of the source tree. --- .github/workflows/build_release.yml | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 57dba975..039ed30b 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -47,24 +47,21 @@ jobs: python -m build twine check --strict dist/* - - name: Install the wheel and run its tests + - name: Install the wheel + run: | + python -m pip install "$(ls dist/*.whl)[dev,tracking]" + mkdir -p /tmp/wheeltest + + - name: Run the wheel's tests uses: aganders3/headless-gui@v2 with: - # --pyargs runs the tests that shipped inside the wheel, and the cd out of - # the repo stops src/ shadowing the installed package - without it this - # would silently retest the source tree and prove nothing. - run: | - python -m pip install "$(ls dist/*.whl)[dev,tracking]" - mkdir -p /tmp/wheeltest && cd /tmp/wheeltest - # pyproject.toml is not in tmp, so pytest has no config - # Keep -o args in sync with [tool.pytest.ini_options]. - python -m pytest --pyargs napari_deeplabcut._tests -q -n auto --dist loadfile \ - -o qt_api=pyside6 \ - -o "markers=e2e: end-to-end tests. Invoke napari viewer fixtures, slow." + working-directory: /tmp/wheeltest + shell: bash + run: >- + python -m pytest --pyargs napari_deeplabcut._tests -q -n auto --dist loadfile + -o qt_api=pyside6 + -o "markers=e2e: end-to-end tests. Invoke napari viewer fixtures, slow." - # Published by test_and_deploy.yml, so the bytes that were tested above are the - # bytes that reach PyPI. Artifacts are scoped to the run, so a caller's other - # jobs can download this. - name: Upload the distribution uses: actions/upload-artifact@v4 with: From f1acf982697d31e8b75038a88bbc30819cbcdb92 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 08:56:01 +0200 Subject: [PATCH 2/8] Restrict releases to version tags Remove the manual `force_deploy` workflow input and simplify the release conditions so the build and deploy jobs only run for `v*` tags. This prevents manual dispatches from attempting package publication from untagged commits, which would fail because PyPI rejects the local versions generated by `setuptools_scm`. --- .github/workflows/test_and_deploy.yml | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 4adfdc49..1102c11b 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -19,14 +19,6 @@ on: branches: - main workflow_dispatch: - inputs: - force_deploy: - # Validates the build and the installed-wheel tests, but does not publish from an - # untagged commit: setuptools_scm derives a local version there (0.x.devN+g) - # and PyPI rejects local version identifiers, so the upload step fails. - description: 'Force deployment even if tests fail' - required: true - type: boolean jobs: test: @@ -118,20 +110,14 @@ jobs: # # this will run when you have tagged a commit, starting with "v*" needs: [test, test-napari-floor] - if: | - always() && ( - (github.event_name == 'workflow_dispatch' && inputs.force_deploy && github.ref == 'refs/heads/main' - ) || - (startsWith(github.ref, 'refs/tags/v') - && needs.test.result == 'success' - && needs['test-napari-floor'].result == 'success') - ) + if: startsWith(github.ref, 'refs/tags/v') uses: ./.github/workflows/build_release.yml deploy: # requires that you have put your twine API key in your # github secrets (see readme for details) needs: [build] + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest concurrency: From 22088336d5e0c7669c6a8ba8de8eefbd296459f4 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 08:56:16 +0200 Subject: [PATCH 3/8] Revmoe redundant marker spec in pytest args --- .github/workflows/build_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 039ed30b..3810ba17 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -60,7 +60,7 @@ jobs: run: >- python -m pytest --pyargs napari_deeplabcut._tests -q -n auto --dist loadfile -o qt_api=pyside6 - -o "markers=e2e: end-to-end tests. Invoke napari viewer fixtures, slow." + -o markers=e2e - name: Upload the distribution uses: actions/upload-artifact@v4 From 8fdb3d16b63ff4170e2f3d70c7a9a15d2854921d Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 09:00:43 +0200 Subject: [PATCH 4/8] Update test_and_deploy.yml --- .github/workflows/test_and_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 1102c11b..ee4c55d5 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -106,7 +106,7 @@ jobs: build: # Shared with the weekly checks, so the release build is continuously exercised # rather than only at tag time. It also builds, checks metadata, installs the - # wheel and runs the suite against it - a release is blocked if any of that fails. + # wheel and runs the suite against it # # this will run when you have tagged a commit, starting with "v*" needs: [test, test-napari-floor] From 212fab55e3945c586c92faefeab1b18b2e63330b Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 09:01:02 +0200 Subject: [PATCH 5/8] Improve weekly CI failure issue reports Expand the weekly checks failure issue workflow to include per-job results, clearer run links, and step-by-step recovery guidance. New issues are now assigned to the regular maintainers, while repeated failures are added as comments with the latest status summary. --- .github/workflows/weekly_checks.yml | 35 ++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/weekly_checks.yml b/.github/workflows/weekly_checks.yml index 3b0d4d55..b4828df2 100644 --- a/.github/workflows/weekly_checks.yml +++ b/.github/workflows/weekly_checks.yml @@ -38,21 +38,46 @@ jobs: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + NAPARI_RESULT: ${{ needs['napari-breakage'].result }} + BUILD_RESULT: ${{ needs['build-release'].result }} run: | set -euo pipefail title="Weekly checks failing" label="weekly checks" gh label create "$label" --repo "$REPO" --color B60205 \ --description "Tracking issue for the weekly CI checks" --force + + summary=$(printf '%s\n' \ + "| Check | Result |" \ + "| --- | --- |" \ + "| napari breakage | $NAPARI_RESULT |" \ + "| build and test release | $BUILD_RESULT |") + existing=$(gh issue list --repo "$REPO" --state open --label "$label" --json number --jq '.[0].number // empty') if [ -n "$existing" ]; then - gh issue comment "$existing" --repo "$REPO" --body "Still failing: $RUN_URL" + comment=$(printf '%s\n' \ + "Still failing: [run $GITHUB_RUN_ID]($RUN_URL)" \ + "" \ + "$summary") + gh issue comment "$existing" --repo "$REPO" --body "$comment" else body=$(printf '%s\n' \ - "Weekly checks failed: $RUN_URL" \ + "Weekly checks failed: [run $GITHUB_RUN_ID]($RUN_URL)" \ + "" \ + "$summary" \ + "" \ + "These run against napari pre-releases and the built wheel, so a failure is usually napari breaking us upstream, or a packaging problem - not a regression from a PR." \ "" \ - "(Run against napari pre-releases and the built wheel.)" \ + "## Resolving" \ "" \ - "Close this issue once the run is fixed.") - gh issue create --repo "$REPO" --title "$title" --body "$body" --label "$label" + "1. Open the failing job from the run above and fix it on a branch." \ + "2. Re-run that check against your branch:" \ + " - Actions -> napari breakage -> Run workflow -> *Plugin commit, tag or branch to test*" \ + " - Actions -> build and test release -> Run workflow -> *Plugin commit, tag or branch to build*" \ + "3. Merge the fix, then dispatch Actions -> weekly checks on main" \ + "4. Close this issue. While it stays open, later failures are added here as comments; once closed, the next failure opens a fresh one.") + url=$(gh issue create --repo "$REPO" --title "$title" --body "$body" --label "$label") + gh issue edit "$url" --repo "$REPO" \ + --add-assignee C-Achard --add-assignee deruyter92 \ + || echo "::warning::could not assign $url" fi From 49c4abe30b1268a6cd5079d1e271713e92966357 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 09:03:57 +0200 Subject: [PATCH 6/8] Add weekly checks failure simulation Allow manual runs of the weekly checks workflow to simulate a failure and exercise the notification path. This skips the real reusable jobs, adds a dedicated failing job, and updates the notify logic and issue content so test runs create clearly labeled tracking issues instead of waiting for a real scheduled failure. --- .github/workflows/weekly_checks.yml | 36 +++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/.github/workflows/weekly_checks.yml b/.github/workflows/weekly_checks.yml index b4828df2..b5906c60 100644 --- a/.github/workflows/weekly_checks.yml +++ b/.github/workflows/weekly_checks.yml @@ -10,22 +10,37 @@ on: # Note: Scheduled workflows only ever run on the default branch - cron: "0 6 * * 1" # Mondays, 06:00 UTC workflow_dispatch: + inputs: + simulate_failure: + description: "Skip the checks, fail on purpose, and open a test tracking issue" + required: false + default: false + type: boolean jobs: napari-breakage: + if: ${{ !inputs.simulate_failure }} uses: ./.github/workflows/napari_breakage.yml build-release: + if: ${{ !inputs.simulate_failure }} uses: ./.github/workflows/build_release.yml + simulate-failure: + # Exercises the notify job without waiting for a real failure. + if: ${{ inputs.simulate_failure }} + runs-on: ubuntu-latest + steps: + - run: exit 1 + notify: # A separate job so the notification cannot be lost to the failure it reports: a # step-level `if: failure()` only runs if its job reaches that step, so a dead # runner, a crashed step or a timeout would silently produce nothing. # - # Scheduled runs only, no issue on manual dispatch. - needs: [napari-breakage, build-release] - if: failure() && github.event_name == 'schedule' + # Scheduled runs only, no issue on manual dispatch unless simulating. + needs: [napari-breakage, build-release, simulate-failure] + if: failure() && (github.event_name == 'schedule' || inputs.simulate_failure) runs-on: ubuntu-latest permissions: contents: read @@ -40,10 +55,18 @@ jobs: RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} NAPARI_RESULT: ${{ needs['napari-breakage'].result }} BUILD_RESULT: ${{ needs['build-release'].result }} + SIMULATED: ${{ inputs.simulate_failure }} run: | set -euo pipefail - title="Weekly checks failing" - label="weekly checks" + if [ "$SIMULATED" = "true" ]; then + title="Weekly checks failing (notification test)" + label="weekly checks test" + banner="> **This is a test.** Opened by a manual dispatch with *simulate_failure* enabled, to exercise this notification. Nothing is broken - close it." + else + title="Weekly checks failing" + label="weekly checks" + banner="" + fi gh label create "$label" --repo "$REPO" --color B60205 \ --description "Tracking issue for the weekly CI checks" --force @@ -76,6 +99,9 @@ jobs: " - Actions -> build and test release -> Run workflow -> *Plugin commit, tag or branch to build*" \ "3. Merge the fix, then dispatch Actions -> weekly checks on main" \ "4. Close this issue. While it stays open, later failures are added here as comments; once closed, the next failure opens a fresh one.") + if [ -n "$banner" ]; then + body=$(printf '%s\n' "$banner" "" "$body") + fi url=$(gh issue create --repo "$REPO" --title "$title" --body "$body" --label "$label") gh issue edit "$url" --repo "$REPO" \ --add-assignee C-Achard --add-assignee deruyter92 \ From 9a8bd9e8478e4313e0c9884aea0b061dbf310b3d Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 21 Sep 2026 09:06:44 +0200 Subject: [PATCH 7/8] Fix format workflow for PR branches Use explicit environment variables and a unique multiline output delimiter when collecting changed files, and check out the pull request head repository in the pre-commit job. This makes the formatting workflow more reliable for pull requests, including ones from forks. --- .github/workflows/format.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 244986e1..8a2460ea 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -18,20 +18,25 @@ jobs: - name: Detect changed files id: changed_files + env: + BASE_REF: ${{ github.base_ref }} run: | - git fetch origin ${{ github.base_ref }} - CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + git fetch origin "$BASE_REF" + CHANGED_FILES=$(git diff --name-only "origin/$BASE_REF...HEAD") + delimiter="changed_$(openssl rand -hex 16)" { - echo "changed<> "$GITHUB_OUTPUT" - name: Show changed files + env: + CHANGED_FILES: ${{ steps.changed_files.outputs.changed }} run: | echo "Changed files:" - echo "${{ steps.changed_files.outputs.changed }}" + echo "$CHANGED_FILES" precommit: needs: detect_changes @@ -43,6 +48,7 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.head_ref }} - name: Set up Python From d0c025b149f2b26c5979acc4abe0890d50b8903c Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Mon, 21 Sep 2026 09:17:56 +0200 Subject: [PATCH 8/8] Check out pull request head SHA instead of head ref Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 8a2460ea..28b7c5e9 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -49,7 +49,7 @@ jobs: with: fetch-depth: 0 repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.head_ref }} + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python uses: actions/setup-python@v6