From b776428588b4b7be08cdd0f72af59a56600681a0 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 26 Aug 2026 10:01:14 -0400 Subject: [PATCH 1/6] build: update CI actions off the deprecated Node 20 runtime Every CI run was warning that these four actions target Node 20 and are being forced onto Node 24: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ actions/checkout v4.3.1 -> v7.0.1 actions/setup-python v5.6.0 -> v7.0.0 astral-sh/setup-uv v4.2.0 -> v10.0.1 codecov/codecov-action v4.6.0 -> v7.0.0 The inputs we pass are all still supported: codecov v7 keeps `token`, `flags`, `fail_ci_if_error` and `working-directory`, and setup-uv is used with no inputs at all. This also gets actions/checkout onto the same major version as the release workflow, which was already on v6. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/backend-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 5521448..cabfe83 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -22,14 +22,14 @@ jobs: python-version: ["3.12"] toxenv: [quality, docs, pii_check, django52, django60] steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: setup python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install CI dependencies run: uv sync --group ci @@ -41,7 +41,7 @@ jobs: - name: Run coverage if: matrix.python-version == '3.12' && matrix.toxenv == 'django52' - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests From a40735469a2753a243eb7a52ecc1ab112861c3c7 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 26 Aug 2026 10:01:26 -0400 Subject: [PATCH 2/6] fix: declare the Django versions we actually support The package advertised `Framework :: Django :: 4.2` on PyPI, but Django 4.2 is not tested anywhere: tox's envlist is `py312-django{52,60}` and CI runs the django52 and django60 environments. Replace it with the two versions we do test. Co-Authored-By: Claude Opus 5 (1M context) --- backend-plugin-sample/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend-plugin-sample/pyproject.toml b/backend-plugin-sample/pyproject.toml index d139be7..2934fc1 100644 --- a/backend-plugin-sample/pyproject.toml +++ b/backend-plugin-sample/pyproject.toml @@ -14,7 +14,8 @@ authors = [ classifiers = [ 'Development Status :: 3 - Alpha', 'Framework :: Django', - 'Framework :: Django :: 4.2', + 'Framework :: Django :: 5.2', + 'Framework :: Django :: 6.0', 'Intended Audience :: Developers', 'Natural Language :: English', 'Programming Language :: Python :: 3', From 5fd1d43ca91f305d12202958a2519caba7d0dfd8 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 26 Aug 2026 10:01:37 -0400 Subject: [PATCH 3/6] build: let dependabot keep our GitHub Actions current Our pinned actions had drifted far enough behind to sit on a deprecated Node runtime, and because we pin to commit SHAs there is nothing that moves them forward on its own. Weekly dependabot PRs keep the pins and their version comments in step without anyone having to notice. Mirrors the config used elsewhere in the org, e.g. openedx-events: https://github.com/openedx/openedx-events/blob/main/.github/dependabot.yml Co-Authored-By: Claude Opus 5 (1M context) --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9e401a1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" From 0feab779817b0ff90b919d9a7153331346eadc4d Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 26 Aug 2026 10:01:51 -0400 Subject: [PATCH 4/6] build: clarify that the release step also publishes the release The comment read "Create a draft release", which suggests the release is left as a draft and something later has to publish it. `gh release create` does the whole sequence itself: it creates the release as a draft, uploads the assets, then publishes it. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ee9ff4..91e0da1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,8 @@ jobs: run: | # Output the release notes to a file printf '%s' "$RELEASE_NOTES" > "$RUNNER_TEMP/release_notes.md" - # Create a draft release + # Creates the release as a draft, uploads the assets, and then + # publishes it -- all within this one command. gh release create "$TAG" \ --verify-tag \ --title "$TAG" \ From 81cd8b2b7f514b818f5f4069239042f52b11edf1 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 26 Aug 2026 10:02:36 -0400 Subject: [PATCH 5/6] build: attach the tutor plugin dists to the GitHub release The release page only carried the backend distributions; the tutor plugin's went to PyPI alone. Build the tutor plugin before the release is created and pass both dist globs to `gh release create` so all four files land on the release. The ordering is load-bearing now: immutable releases freeze the asset list at publish time, so anything we want attached has to exist before that single command runs. Asset names don't collide -- the two projects are `openedx-plugin-sample` and `tutor-contrib-sample`. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91e0da1..f03a32f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,10 +50,23 @@ jobs: # are attached before the release is published. See that step for why. vcs_release: "false" + # Built before the release is created so that its distributions can be + # attached as release assets too. See the release step for why that + # ordering matters. + - name: Build | Tutor Plugin + # The tutor plugin does not use python-semantic-release directly. + # Instead it shares the version determined by the backend's semantic + # release above, passed in via SETUPTOOLS_SCM_PRETEND_VERSION so that + # setuptools-scm picks it up at build time. + if: steps.release.outputs.released == 'true' + run: pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=${{ steps.release.outputs.version }} python -m build + working-directory: './tutor-contrib-sample' + # This repo has immutable releases enabled, which freezes a release's # assets the moment it is published, so assets cannot be attached - # afterwards. `gh release create` handles this by creating the release as - # a draft, uploading the assets, and only then publishing it: + # afterwards. Everything we want on the release has to be built by now + # and passed to this one command, which creates the release as a draft, + # uploads the assets, and only then publishes it: # https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases - name: Publish | Create GitHub Release with Assets if: steps.release.outputs.released == 'true' @@ -71,7 +84,8 @@ jobs: --verify-tag \ --title "$TAG" \ --notes-file "$RUNNER_TEMP/release_notes.md" \ - backend-plugin-sample/dist/* + backend-plugin-sample/dist/* \ + tutor-contrib-sample/dist/* - name: Upload | Backend Distribution Artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -81,15 +95,6 @@ jobs: path: backend-plugin-sample/dist if-no-files-found: error - - name: Build | Tutor Plugin - # The tutor plugin does not use python-semantic-release directly. - # Instead it shares the version determined by the backend's semantic - # release above, passed in via SETUPTOOLS_SCM_PRETEND_VERSION so that - # setuptools-scm picks it up at build time. - if: steps.release.outputs.released == 'true' - run: pip install build && SETUPTOOLS_SCM_PRETEND_VERSION=${{ steps.release.outputs.version }} python -m build - working-directory: './tutor-contrib-sample' - - name: Upload | Tutor Plugin Distribution Artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: steps.release.outputs.released == 'true' From a2e9713736464e712fbbbebb9c479de0455333e2 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 26 Aug 2026 10:12:54 -0400 Subject: [PATCH 6/6] build: give each CI matrix job its own uv cache key setup-uv v10 enables caching by default, so all five matrix jobs raced to save the same cache key and four of them warned: Failed to save: Unable to reserve cache with key setup-uv-2-x86_64-unknown-linux-gnu-... another job may be creating this cache. Each job runs a different toxenv and therefore populates the uv cache with a different set of dependencies, so a per-toxenv key is both quieter and a better fit than one shared key. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/backend-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index cabfe83..9b36c2a 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -30,6 +30,11 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + # Every matrix job populates the uv cache with a different set of tox + # dependencies, so give each one its own cache key. Sharing a single + # key makes the jobs race to save it, and all but the winner warn. + cache-suffix: ${{ matrix.toxenv }} - name: Install CI dependencies run: uv sync --group ci