Skip to content

CI: resolve the environment fresh weekly, and test pull requests - #13

Merged
cleemansen merged 4 commits into
mainfrom
claude/work-item-7-908f21
Aug 4, 2026
Merged

cleemansen merged 4 commits into
mainfrom
claude/work-item-7-908f21

Conversation

@cleemansen

@cleemansen cleemansen commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Closes the CI half of couchbits/movestore/movestore-groundcontrol#7.

1. A cached conda layer could report green against an unchecked resolution

Since v2.2.0 the environment is open by design — only pandas carries a floor and a ceiling:

- python
- pandas>=2.3.3,<3
- geopandas
- movingpandas

test/Dockerfile resolves that environment in its own layer, before the sources are copied. The
layering is right — it is what keeps a source-only change cheap — but it means that while the layer
came from the Actions cache, the suite passed against a resolution nobody had re-checked.

Nothing evicted it, either. The cache drops entries idle for seven days, and every push read it via
cache-from, which reset the timer. In an active repo the stale layer survives until
environment.yml changes — precisely the moment nobody is watching for an unrelated dependency to
have moved underneath. The same mechanic already bit us once during rollout, when an App build pulled
its conda layer from the kaniko cache and completed "successfully" against the old environment.

Scheduled (Mondays 04:17 UTC) and manually dispatched runs now build with no-cache and without
cache-from, then publish the fresh resolution — so ordinary runs test an environment at most a week
old rather than an arbitrarily old one.

2. Pull requests were never tested

The workflow triggered on push only, so a pull request from a fork never ran. For a repo whose
purpose is to be copied by external contributors, that is the likelier origin of a change than a
branch in this repo. pull_request is now a trigger.

push narrows to main at the same time, so a branch here with an open PR stops building twice.

Three things that are not obvious from the diff

  • cache-from is dropped on fresh runs, not merely overridden by no-cache. --no-cache ought
    to outrank an imported cache, but that precedence is documented nowhere, and it is the single
    assumption the whole guard rests on. Removing the import removes the question.
  • The cache-from ternary is inverted deliberately. The natural spelling
    ${{ FRESH == 'true' && '' || 'type=gha' }} is a trap: '' is falsy in GitHub expressions, so
    the || branch always wins and both paths silently get the cache back — reinstating the exact
    false green this change exists to remove. Keeping the non-empty string on the truthy side is what
    makes it work.
  • ignore-error=true on the export. A fork PR runs with a read-only token and cannot write the
    Actions cache, so cache-to 403s. Without it the run dies at the very end, after the tests have
    already passed — a red check for a green suite. Import is unaffected, so fork PRs stay warm.

permissions stays contents: read; cache export authenticates via the Actions runtime token, not
the permissions block. The trigger is pull_request, not pull_request_target — fork code runs
with a read-only token and no secrets, which is what makes building arbitrary contributor
Dockerfiles safe.

Known limitation

GitHub disables schedule: triggers in a repo with no commits for 60 days. A quiet repo therefore
loses the weekly canary silently — the failure mode is no signal rather than a red build, which is
the worse of the two. There is no clean fix inside Actions; it needs an external ping or a periodic
human check. Related: scheduled-run failure notifications go only to whoever last edited the cron
line.

Verification

Static gates pass locally (actionlint, plus yq assertions pinning the trap expression). The
pull_request run on this PR is the first end-to-end evidence. The fresh path can only be exercised
after merge — GitHub offers workflow_dispatch and schedule only for a workflow already on the
default branch — and the check that matters there is that conda env create rebuilds without a
CACHED marker.

Reasoning is recorded in
docs/superpowers/specs/2026-08-04-ci-fresh-resolution-and-pr-trigger-design.md.

🤖 Generated with Claude Code


Nachtrag: was das Verifizieren noch zutage gefördert hat

Der erste pull_request-Lauf war grün — in 21 Sekunden, mit jeder Schicht CACHED, auch der,
die die Tests ausführt:

#10 [4/6] RUN conda env create --prefix ... && conda clean --all --yes
#10 CACHED
#12 [6/6] RUN conda run ... python -m unittest python.tests.test_transform_to_pickle ...
#12 CACHED

Das Ticket beschreibt die Auflösung als veraltet. Tatsächlich cached auch die Test-Schicht: bei
unverändertem Baum läuft kein einziger Test. Grün heißt dann „nichts war neu zu bauen", nicht „die
Tests sind durch". Das braucht keinen eigenen Fix — eine Änderung an python/ oder
environment.yml invalidiert die jeweilige Schicht und die Tests laufen wieder. Es verschiebt aber
den Zweck des wöchentlichen Laufs: er ist nicht nur der einzige, der neu auflöst, sondern bei
unverändertem Baum überhaupt der einzige, der die Suite ausführt.

Drei Dinge aus derselben blinden Ecke sind gleich mit drin:

  • Action-Majors: actions/checkout v4 → v7, docker/setup-buildx-action v3 → v4,
    docker/build-push-action v6 → v7. Jeder Lauf warnte, dass diese Versionen Node 20 adressieren
    und zwangsweise auf Node 24 laufen. Die Warnung ist im Folgelauf verschwunden. Der einzige echte
    Breaking Change — checkout v7 verweigert Fork-Checkout unter pull_request_target und
    workflow_run — trifft uns nicht, wir nutzen beides nicht.
  • .github/dependabot.yml, nur github-actions, alle Bumps in einem PR gruppiert. Drei Majors
    Drift sind unbemerkt geblieben, weil nichts die Pins beobachtet. Docker bleibt bewusst aus: das
    App-Dockerfile zieht aus einer privaten GitLab-Registry, an der Dependabot sich nicht
    authentifizieren kann.
  • timeout-minutes: 30. GitHubs Default ist 360. Egal, solange jeder Lauf ein Cache-Hit war —
    aber der wöchentliche Lauf löst jetzt unbeaufsichtigt echt auf, und ein hängender Solver würde
    sonst jeden Montag sechs Stunden Runner-Zeit verbrennen.

Nicht enthalten, bewusst: R-Tests (es existieren keine — die CI deckt nur die Python-Hälfte ab),
environment.dev.yml im CI-Build, und ein Issue-Report bei fehlgeschlagenem schedule-Lauf. Alle
drei sind in der Spec unter Out of scope mit Begründung festgehalten.

cleemansen and others added 4 commits August 4, 2026 12:23
The environment is open by design since v2.2.0, so a cached `conda env create`
layer lets the suite report green against a resolution nobody has re-checked.
The spec records why the fresh runs drop `cache-from` outright rather than
trusting `no-cache` to outrank it, and why the cache-from ternary is inverted.

Narrows the inherited pkgdown `docs/` rule so the spec is trackable; this repo
has no pkgdown site, and the rule was only ever meant for generated output.

Refs couchbits/movestore/movestore-groundcontrol#7

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the sequencing constraint that shapes the order: `workflow_dispatch`
and `schedule` only exist for a workflow already on the default branch, so the
fresh-resolution path cannot be verified until after the merge, while
`pull_request` verifies straight from the PR branch.

Refs couchbits/movestore/movestore-groundcontrol#7

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`test/Dockerfile` resolves conda in its own layer, and since v2.2.0 that
environment is open by design - only `pandas` is pinned. So a cached layer let
the suite pass against a resolution nobody had re-checked, and nothing evicted
it: every push's `cache-from` reset GitHub's seven-day idle timer. Scheduled
and dispatched runs now build with `no-cache` and without `cache-from`, then
publish the fresh result, which also caps how stale an ordinary run can be.

Pull requests were not tested at all. For a repo meant to be copied, a fork PR
is the likelier origin of a change than a branch here, so `pull_request` is now
a trigger and `cache-to` tolerates the 403 a fork's read-only token earns.

`push` narrows to `main` so a branch with an open PR stops building twice.

Refs couchbits/movestore/movestore-groundcontrol#7

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verifying the trigger change surfaced three things in the same blind spot, so
they land together.

Every run warned that `actions/checkout@v4`, `docker/setup-buildx-action@v3`
and `docker/build-push-action@v6` target Node 20 and are being force-migrated
to Node 24. All three majors are substantively that runtime switch; `checkout`
v7 additionally refuses to check out fork code under `pull_request_target` and
`workflow_run`, neither of which this workflow uses.

That drift - three majors on checkout - went unnoticed because nothing watches
the pins, so add `dependabot.yml` for `github-actions`, grouped into one pull
request. Docker is left off on purpose: the App `Dockerfile` pulls from a
private GitLab registry Dependabot cannot authenticate against.

`timeout-minutes: 30` because GitHub's default is 360. That was harmless while
every run was a 21-second cache hit, but the weekly run resolves conda for
real and unattended, and a hung solver would burn six hours every Monday.

The spec also records what the first PR run proved: the `unittest` layer is
cached like any other, so a green check on an unchanged tree runs no tests at
all. That does not need fixing - a real change invalidates the layer - but it
means the weekly run is the only periodic evidence the suite still passes.

Refs couchbits/movestore/movestore-groundcontrol#7

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cleemansen cleemansen self-assigned this Aug 4, 2026
@cleemansen
cleemansen merged commit a7a419a into main Aug 4, 2026
2 checks passed
@cleemansen
cleemansen deleted the claude/work-item-7-908f21 branch August 4, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant