From 327a053e8a76096d369644a202f22e5ee42d686c Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Wed, 15 Jul 2026 14:22:59 +0200 Subject: [PATCH 1/2] docs(ci): persist in-container tool caches + verify conditional trigger paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two additions to ci-runner-capacity.md: (1) persisting a tool cache written inside 'docker compose run --rm' by pointing tmpDir/cacheDirectory at a host-mounted path + actions/cache keyed on run_id with prefix restore-keys (save-fresh + restore-latest; tools self-invalidate so a stale restore is safe); (2) a green run of one conditional path (PR) does not prove the schedule/dispatch path — exercise every trigger path (workflow_dispatch only fires on the default branch, so verify after merge). Signed-off-by: Sebastian Mendel --- .../references/ci-runner-capacity.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/skills/github-project/references/ci-runner-capacity.md b/skills/github-project/references/ci-runner-capacity.md index 563c3b4..ec1764d 100644 --- a/skills/github-project/references/ci-runner-capacity.md +++ b/skills/github-project/references/ci-runner-capacity.md @@ -77,3 +77,33 @@ Even when it buys no wall clock, halving job-minutes lowers org-wide queue pressure for *every* repo sharing the pool — so making jobs faster is the right first move; just don't promise a wall-clock win it can't deliver under the acquisition-bound regime. + +## Persist tool caches written inside `docker compose run --rm` + +A linter/analyzer run inside `docker compose run --rm` writes to the container's +ephemeral filesystem — anything under the container's `/tmp` is discarded every +run and is uncacheable. Point the tool's cache at a **host-mounted** repo path +(PHPStan `tmpDir: var/cache/phpstan`, Rector `->withCache('var/cache/rector')`), +then `actions/cache` it: + +- **Key** on `${{ github.run_id }}` (unique → never hits → always saves a fresh + cache post-job) with a prefix `restore-keys:` (restores the most recent prior + cache) — save-fresh + restore-latest = incremental warmth. +- **Safe stale restore:** the tools self-invalidate (PHPStan salts on + config+composer+PHP; Rector on the config-file hash), so a restored-but-stale + cache only recomputes changed files, never a wrong result — so *don't* bake a + content hash into the key; it just fragments the cache lineage. +- **uid:** the container writes as root (umask 022 → 0644 files), so the post-job + save (runner user) reads them without a chmod. + +Measured: a PHPStan-heavy Lint job dropped ~145s cold → ~72s warm. + +## Verify every trigger path after a conditional change + +When a workflow behaves differently by `github.event_name` / a flag / a matrix +cell, a green run of ONE path is not "done" — each path is separate code. Gating +coverage steps on `schedule` leaves the `pull_request` run green while the +coverage-on (`schedule`) path still hides a bug (e.g. a job that now exceeds its +`timeout-minutes`). Exercise the non-PR path on demand with `workflow_dispatch` +— but it only fires for the workflow version on the **default branch**, so that +path can often only be verified *after* merge. Check it then; don't assume. From 55e6a3c2f1ddd9244daff2f80217b3eebcb7ce04 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Wed, 15 Jul 2026 14:35:01 +0200 Subject: [PATCH 2/2] docs(ci): clarify restore-keys wording + workflow_dispatch nuance (review) Signed-off-by: Sebastian Mendel --- skills/github-project/references/ci-runner-capacity.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/skills/github-project/references/ci-runner-capacity.md b/skills/github-project/references/ci-runner-capacity.md index ec1764d..5115256 100644 --- a/skills/github-project/references/ci-runner-capacity.md +++ b/skills/github-project/references/ci-runner-capacity.md @@ -87,8 +87,8 @@ run and is uncacheable. Point the tool's cache at a **host-mounted** repo path then `actions/cache` it: - **Key** on `${{ github.run_id }}` (unique → never hits → always saves a fresh - cache post-job) with a prefix `restore-keys:` (restores the most recent prior - cache) — save-fresh + restore-latest = incremental warmth. + cache post-job) with `restore-keys` set to the key's prefix (restores the most + recent prior cache) — save-fresh + restore-latest = incremental warmth. - **Safe stale restore:** the tools self-invalidate (PHPStan salts on config+composer+PHP; Rector on the config-file hash), so a restored-but-stale cache only recomputes changed files, never a wrong result — so *don't* bake a @@ -105,5 +105,7 @@ cell, a green run of ONE path is not "done" — each path is separate code. Gati coverage steps on `schedule` leaves the `pull_request` run green while the coverage-on (`schedule`) path still hides a bug (e.g. a job that now exceeds its `timeout-minutes`). Exercise the non-PR path on demand with `workflow_dispatch` -— but it only fires for the workflow version on the **default branch**, so that -path can often only be verified *after* merge. Check it then; don't assume. +— but the trigger must already exist on the **default branch** to be +dispatchable. If it's *new in the PR*, that path is only verifiable after merge; +if it already exists on the default branch, you can dispatch the PR branch's +version directly. Either way, check it; don't assume.