Plugin CI: one shared definition for the seven plugin workflows, with uv's cache and no second run on a tag #1170

Open
opened 2026-10-31 03:04:29 +00:00 by tiagoagueda · 2 comments
Owner

This issue holds 3 findings of the audit that share one fix or the same files.

PC7: Plugin CI: seven copies of one workflow drift apart, and a uv or core bump is fifteen hand edits across eight repositories

What exists today

Read from the server's main of each repository (fetched 2026-10-30; the local checkouts are two to three weeks behind and gave the scout wrong details). The seven Python plugin ci.yml files (101-137 lines each) are one template copied by hand, and they have drifted:

  • The dependency audit is a step of the test job in imap, dav and francetravail, and a separate job in paperless, templates, helloworld and apprise (named "Audit the locked dependencies" or "Security audit"); apprise alone runs it with --no-project.
  • A weekly schedule exists in apprise, templates and helloworld; only apprise skips the test job on it (PC3), so the other two run the full suite every Monday on an unchanged lock.
  • tags: ["v*"] is in six of seven (PC6); templates installs an extra library (libharfbuzz-subset0); apprise has an every-python.yml the others lack.
  • The release.yml files are the same template again, with differently worded steps.

Corrections to the candidate: the core pin is no longer five SHAs; all seven pin a8467ab21 (2026-10-23, 39 commits behind core main). The text of #1090 still lists the old five pins and should be refreshed before that round starts. The uv pin is the same everywhere (PC2 refuted).

The bump procedure: CONTRIBUTING.md lines 626-634 say bumping uv takes four edits "in the five workflows" of the core, and tests/test_supply_chain_pins.py refuses a half-done one there; the plugin comments say "bump both together, with the core", but nothing lists the plugin files or checks them, so a uv bump is about fifteen more edits (eight ci.yml, six release.yml, apprise's every-python.yml) in eight repositories, each through its own pull request. Moving the core pin (#1090) is likewise seven pyproject.toml and seven uv.lock changes with no written procedure.

What would change

  • One definition of the plugin job. Options, in the house's order of preference:
    1. A composite action in a repository on this Forgejo (for example postulo/plugin-ci), referenced by full address, uses: https://source.tiagoagueda.com/postulo/plugin-ci@v1, which Forgejo Actions accepts. A bare name is not safe: the core's ci.yml comment (lines 8-16) says bare names resolve against "Forgejo's own copy", but postulo-firefox's ci.yml (lines 34-38) says this instance serves actions from data.forgejo.org, so a bare postulo/plugin-ci might be looked up there. Each plugin's ci.yml shrinks to checkout plus one step, and the uv version and hash live in one place.
    2. A reusable workflow (jobs.test.uses: postulo/plugin-ci/.forgejo/workflows/plugin.yml@v1), if this Forgejo version supports cross-repository reusable workflows; to be tried on one repository first.
    3. A scripts/plugin_ci.py in the core that renders the seven files from one template and a check that they match, run when the core bumps uv.
  • A short "Moving the plugins to a new core" section in CONTRIBUTING.md (or the wiki's Writing a plugin), with the uv and pin bumps listed for every repository.
  • Settle the drift while doing it: audit as its own job everywhere, the weekly schedule in all seven with the test job skipped on it, no tag trigger in ci.yml (PC6), the uv cache (PC1).

What it saves or protects

medium (maintenance and reliability): one place to change the plugin CI instead of seven, and drift caught instead of discovered

Accessibility

none

Decided by the maintainer (2026-10-31)

shared plugin CI as a composite action in its own repo, referenced by full address, tried on helloworld first; one core issue carrying PC1 and PC6

Decisions

  • Composite action, reusable workflow or generator script? Recommended: a composite action in its own repository, tagged and referenced by full address; try it on postulo-helloworld first.
  • Split? Recommended: one core issue naming all seven repositories, carrying PC1 and PC6 as items, since all three change the same files.

PC1: Plugin CI: no plugin keeps uv's cache, so every run fetches the core's git repository again, about 90 s of a 3-minute job

What exists today

Read from each repository's main on the server (the local checkouts are two weeks behind; files fetched through the API on 2026-10-30): postulo-imap, -dav, -paperless, -templates, -helloworld, -apprise and tiagoagueda/postulo-francetravail all pin the core in their dev group as postulo @ git+https://source.tiagoagueda.com/postulo/postulo.git@a8467ab21… (the same commit in all seven) and install with uv sync --locked --python 3.14 (ci.yml line 65 in imap). None uses actions/cache (grep over every plugin workflow); the core does, for ~/.cache/uv and the browser (postulo/.forgejo/workflows/ci.yml lines 120-128 and 247-252), continue-on-error: true, with restore keys withheld from pull requests.

Measured from the log of postulo-imap run 81 (push to main, 2026-10-25), timestamps in the log:

  • Updating https://source.tiagoagueda.com/postulo/postulo.git (a8467ab21…) at 13:28:57.9, Updated at 13:30:25.5: 88 s cloning the core.
  • Prepared 63 packages in 1m 30s, Installed 63 packages in 18.41s, Python 3.14 downloaded (34 MiB) as well.
  • The whole job: 3 min 12 s; the test suite itself 38.6 s.

The other repositories' test jobs take 2.5-4 minutes on the same pattern (task list through the API). The release workflows (release.yml) repeat the same install on a tag. The candidate's "96 MB tree" is the core's checkout; uv clones it into its cache, which is what a cache would keep.

Not covered by #1033 (core CI only) or #1090 (pins and tags).

What would change

In each plugin ci.yml (and release.yml), before uv sync, the core's own cache step:

- uses: actions/cache@v4
  continue-on-error: true
  with:
    path: |
      ~/.cache/uv
      ~/.local/share/uv/python
    key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
    restore-keys: ${{ github.event_name != 'pull_request' && format('uv-{0}-', runner.os) || '' }}

uv's git cache holds the fetched core, so a run whose lock did not change skips the clone and the downloads. A further step, outside this issue: once the core publishes wheels to the Forgejo package registry with its releases (#1090 moves the pins to tags), the dev group can name the wheel instead of git, and no clone happens even without a cache.

What it saves or protects

large per run: about 90 s of fetch and 20 s of downloads in each of seven repositories' test jobs (postulo-imap run 81: 1 min 30 s of 3 min 12 s preparing packages)

Accessibility

none

Decisions

  • One issue on the core naming all seven repositories, or one per repository? Recommended: one on the core (the plan's rule for org-wide plugin CI), with a checklist per repository; it lands together with PC7 if a shared workflow is chosen.

PC6: Plugin CI: a tag runs the whole CI again beside the release gate, on a commit main already tested

What exists today

On the server's main, ci.yml line 17 has push: tags: ["v*"] in postulo-apprise, -dav, -francetravail, -imap, -paperless and -templates (postulo-helloworld dropped it). Each of these also has release.yml, triggered by the same tag, which checks out, installs the WeasyPrint libraries, installs uv, runs uv sync --locked and pytest -m release. A tag push therefore starts two workflows that each clone the core and install everything (about 1 min 50 s of install alone, PC1), and the CI one re-runs lint, the catalogue step and the full suite on the commit that main's own push already tested, since every change reaches main through a green pull request (#940).

No plugin is tagged on its main CI history yet, so the cost has not been paid; #1090 is about to tag every plugin.

What would change

Remove tags: ["v*"] from on.push in the six ci.yml files, leaving release.yml as the only tag workflow, as postulo-helloworld already does. If a tag should still prove the suite, add uv run pytest before pytest -m release in release.yml instead, so the install happens once.

What it saves or protects

small: one full install and test run (about 3 minutes of the shared runner) saved per plugin tag; the round of tags #1090 plans would be the first to pay it

Accessibility

none

Decisions

  • File on its own or as a comment on #1090? Recommended: as a line in PC7's issue (it is a change to the same seven workflow files); otherwise a comment on #1090 before its tagging round.

Found by the efficiency and reliability audit of 2026-10-30 (finding PC7, PC1, PC6; index postulo/postulo#1108). Checked against main at daee30b15 (2026-10-30).

This issue holds 3 findings of the audit that share one fix or the same files. ## PC7: Plugin CI: seven copies of one workflow drift apart, and a uv or core bump is fifteen hand edits across eight repositories ### What exists today Read from the server's `main` of each repository (fetched 2026-10-30; the local checkouts are two to three weeks behind and gave the scout wrong details). The seven Python plugin `ci.yml` files (101-137 lines each) are one template copied by hand, and they have drifted: - The dependency audit is a step of the test job in imap, dav and francetravail, and a separate job in paperless, templates, helloworld and apprise (named "Audit the locked dependencies" or "Security audit"); apprise alone runs it with `--no-project`. - A weekly schedule exists in apprise, templates and helloworld; only apprise skips the test job on it (PC3), so the other two run the full suite every Monday on an unchanged lock. - `tags: ["v*"]` is in six of seven (PC6); templates installs an extra library (`libharfbuzz-subset0`); apprise has an `every-python.yml` the others lack. - The `release.yml` files are the same template again, with differently worded steps. Corrections to the candidate: the core pin is no longer five SHAs; all seven pin `a8467ab21` (2026-10-23, 39 commits behind core `main`). The text of #1090 still lists the old five pins and should be refreshed before that round starts. The uv pin is the same everywhere (PC2 refuted). The bump procedure: `CONTRIBUTING.md` lines 626-634 say bumping uv takes four edits "in the five workflows" of the core, and `tests/test_supply_chain_pins.py` refuses a half-done one there; the plugin comments say "bump both together, with the core", but nothing lists the plugin files or checks them, so a uv bump is about fifteen more edits (eight `ci.yml`, six `release.yml`, apprise's `every-python.yml`) in eight repositories, each through its own pull request. Moving the core pin (#1090) is likewise seven `pyproject.toml` and seven `uv.lock` changes with no written procedure. ### What would change - One definition of the plugin job. Options, in the house's order of preference: 1. A composite action in a repository on this Forgejo (for example `postulo/plugin-ci`), referenced by full address, `uses: https://source.tiagoagueda.com/postulo/plugin-ci@v1`, which Forgejo Actions accepts. A bare name is not safe: the core's `ci.yml` comment (lines 8-16) says bare names resolve against "Forgejo's own copy", but postulo-firefox's `ci.yml` (lines 34-38) says this instance serves actions from data.forgejo.org, so a bare `postulo/plugin-ci` might be looked up there. Each plugin's `ci.yml` shrinks to checkout plus one step, and the uv version and hash live in one place. 2. A reusable workflow (`jobs.test.uses: postulo/plugin-ci/.forgejo/workflows/plugin.yml@v1`), if this Forgejo version supports cross-repository reusable workflows; to be tried on one repository first. 3. A `scripts/plugin_ci.py` in the core that renders the seven files from one template and a check that they match, run when the core bumps uv. - A short "Moving the plugins to a new core" section in `CONTRIBUTING.md` (or the wiki's *Writing a plugin*), with the uv and pin bumps listed for every repository. - Settle the drift while doing it: audit as its own job everywhere, the weekly schedule in all seven with the test job skipped on it, no tag trigger in `ci.yml` (PC6), the uv cache (PC1). ### What it saves or protects medium (maintenance and reliability): one place to change the plugin CI instead of seven, and drift caught instead of discovered ### Accessibility none ### Decided by the maintainer (2026-10-31) shared plugin CI as a composite action in its own repo, referenced by full address, tried on helloworld first; one core issue carrying PC1 and PC6 ### Decisions - **Composite action, reusable workflow or generator script?** Recommended: a composite action in its own repository, tagged and referenced by full address; try it on postulo-helloworld first. - **Split?** Recommended: one core issue naming all seven repositories, carrying PC1 and PC6 as items, since all three change the same files. ## PC1: Plugin CI: no plugin keeps uv's cache, so every run fetches the core's git repository again, about 90 s of a 3-minute job ### What exists today Read from each repository's `main` on the server (the local checkouts are two weeks behind; files fetched through the API on 2026-10-30): postulo-imap, -dav, -paperless, -templates, -helloworld, -apprise and tiagoagueda/postulo-francetravail all pin the core in their dev group as `postulo @ git+https://source.tiagoagueda.com/postulo/postulo.git@a8467ab21…` (the same commit in all seven) and install with `uv sync --locked --python 3.14` (`ci.yml` line 65 in imap). None uses `actions/cache` (grep over every plugin workflow); the core does, for `~/.cache/uv` and the browser (`postulo/.forgejo/workflows/ci.yml` lines 120-128 and 247-252), `continue-on-error: true`, with restore keys withheld from pull requests. Measured from the log of postulo-imap run 81 (push to main, 2026-10-25), timestamps in the log: - `Updating https://source.tiagoagueda.com/postulo/postulo.git (a8467ab21…)` at 13:28:57.9, `Updated` at 13:30:25.5: 88 s cloning the core. - `Prepared 63 packages in 1m 30s`, `Installed 63 packages in 18.41s`, Python 3.14 downloaded (34 MiB) as well. - The whole job: 3 min 12 s; the test suite itself 38.6 s. The other repositories' test jobs take 2.5-4 minutes on the same pattern (task list through the API). The release workflows (`release.yml`) repeat the same install on a tag. The candidate's "96 MB tree" is the core's checkout; uv clones it into its cache, which is what a cache would keep. Not covered by #1033 (core CI only) or #1090 (pins and tags). ### What would change In each plugin `ci.yml` (and `release.yml`), before `uv sync`, the core's own cache step: ```yaml - uses: actions/cache@v4 continue-on-error: true with: path: | ~/.cache/uv ~/.local/share/uv/python key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} restore-keys: ${{ github.event_name != 'pull_request' && format('uv-{0}-', runner.os) || '' }} ``` uv's git cache holds the fetched core, so a run whose lock did not change skips the clone and the downloads. A further step, outside this issue: once the core publishes wheels to the Forgejo package registry with its releases (#1090 moves the pins to tags), the dev group can name the wheel instead of git, and no clone happens even without a cache. ### What it saves or protects large per run: about 90 s of fetch and 20 s of downloads in each of seven repositories' test jobs (postulo-imap run 81: 1 min 30 s of 3 min 12 s preparing packages) ### Accessibility none ### Decisions - **One issue on the core naming all seven repositories, or one per repository?** Recommended: one on the core (the plan's rule for org-wide plugin CI), with a checklist per repository; it lands together with PC7 if a shared workflow is chosen. ## PC6: Plugin CI: a tag runs the whole CI again beside the release gate, on a commit `main` already tested ### What exists today On the server's `main`, `ci.yml` line 17 has `push: tags: ["v*"]` in postulo-apprise, -dav, -francetravail, -imap, -paperless and -templates (postulo-helloworld dropped it). Each of these also has `release.yml`, triggered by the same tag, which checks out, installs the WeasyPrint libraries, installs uv, runs `uv sync --locked` and `pytest -m release`. A tag push therefore starts two workflows that each clone the core and install everything (about 1 min 50 s of install alone, PC1), and the CI one re-runs lint, the catalogue step and the full suite on the commit that `main`'s own push already tested, since every change reaches `main` through a green pull request (#940). No plugin is tagged on its `main` CI history yet, so the cost has not been paid; #1090 is about to tag every plugin. ### What would change Remove `tags: ["v*"]` from `on.push` in the six `ci.yml` files, leaving `release.yml` as the only tag workflow, as postulo-helloworld already does. If a tag should still prove the suite, add `uv run pytest` before `pytest -m release` in `release.yml` instead, so the install happens once. ### What it saves or protects small: one full install and test run (about 3 minutes of the shared runner) saved per plugin tag; the round of tags #1090 plans would be the first to pay it ### Accessibility none ### Decisions - **File on its own or as a comment on #1090?** Recommended: as a line in PC7's issue (it is a change to the same seven workflow files); otherwise a comment on #1090 before its tagging round. --- Found by the efficiency and reliability audit of 2026-10-30 (finding PC7, PC1, PC6; index postulo/postulo#1108). Checked against `main` at `daee30b15` (2026-10-30).
tiagoagueda added this to the 0.6.0 milestone 2026-10-31 03:04:29 +00:00
Author
Owner

Go-ahead from the maintainer (2026-11-12)

Make the shared plugin CI: a composite action in its own repository on this Forgejo (Postulo/plugin-ci), referenced by full address and tagged, tried on postulo-helloworld first, then the other plugin repositories, carrying PC1 (uv's cache) and PC6 (no CI run on a tag).

🤖 Generated with Claude Code

## Go-ahead from the maintainer (2026-11-12) Make the shared plugin CI: a composite action in its own repository on this Forgejo (`Postulo/plugin-ci`), referenced by full address and tagged, tried on postulo-helloworld first, then the other plugin repositories, carrying PC1 (uv's cache) and PC6 (no CI run on a tag). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

The shared plugin CI is in place: the action Postulo/plugin-ci@v1 (repository created and tagged 2026-11-12) runs CI in all seven Python plugins: helloworld first (its first run green: uv checksum, a full cache key, catalogue checks, tests), then imap, dav, paperless, templates, apprise and francetravail. uv is pinned in one place, uv's cache is kept (PC1), no CI runs on a tag (PC6), and the audit is its own job and runs weekly. Status-check names are unchanged.

Left for the core: CONTRIBUTING's uv-bump section should name plugin-ci/action.yml as the one place the plugins' uv pin lives.

🤖 Generated with Claude Code

The shared plugin CI is in place: the action `Postulo/plugin-ci@v1` (repository created and tagged 2026-11-12) runs CI in all seven Python plugins: helloworld first (its first run green: uv checksum, a full cache key, catalogue checks, tests), then imap, dav, paperless, templates, apprise and francetravail. uv is pinned in one place, uv's cache is kept (PC1), no CI runs on a tag (PC6), and the audit is its own job and runs weekly. Status-check names are unchanged. Left for the core: CONTRIBUTING's uv-bump section should name `plugin-ci/action.yml` as the one place the plugins' uv pin lives. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#1170
No description provided.