From f48905398ca6ed88a67bcd18071abdb046c270c6 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Fri, 5 Jun 2026 20:26:33 -0700 Subject: [PATCH] refactor(ci): plumb regenerated PR lockfile via artifact (#7629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies > - The CI pipeline (`.github/workflows/pr.yml`) gates every PR with a `policy` job and a fleet of downstream test/build/release jobs > - `policy` blocks committing `pnpm-lock.yaml` from non-dependabot, non-`chore/refresh-lockfile` branches and (when manifests change) already regenerates the lockfile in memory — but throws it away > - Every downstream job runs `pnpm install --frozen-lockfile`, so manifest-only PRs from human/agent branches deadlock: they can't commit the lockfile and can't pass `--frozen-lockfile` either, and `refresh-lockfile.yml` only runs `on: push: master` > - This pull request closes the loop by uploading the regenerated lockfile as a workflow artifact (`pr-lockfile`) from `policy` and restoring it in each downstream job before `pnpm install --frozen-lockfile` runs > - The benefit is that any manifest-only PR (e.g. a routine dep bump) goes green end-to-end without a separate refresh-lockfile round-trip, and unrelated PRs are unaffected because the download step is best-effort (`continue-on-error: true`) ## What Changed - `.github/workflows/pr.yml::policy`: add `id: regen_lockfile` to the existing manifest-detection step, emit a `regenerated` output, and conditionally `actions/upload-artifact@v4` `pnpm-lock.yaml` as `pr-lockfile` (retention 1 day) when a manifest changed - `.github/workflows/pr.yml` (every downstream job — `typecheck_release_registry`, `general_tests` [3 matrix], `build`, `verify_serialized_server` [4 matrix], `canary_dry_run`, `e2e`): add a `Restore regenerated PR lockfile (if policy uploaded one)` step using `actions/download-artifact@v4` with `continue-on-error: true`, placed before the existing `pnpm install --frozen-lockfile` - `.github/workflows/pr.yml::canary_dry_run`: guard the pre-existing `git checkout -- pnpm-lock.yaml` so it no longer clobbers an artifact-restored lockfile when one is present ## Verification - `node -e "require('js-yaml').load(...)"` parses the file; all 6 downstream jobs have the restore step, `policy` has the upload step - This branch is itself a manifest-untouched PR, so `policy` will not upload an artifact and every downstream job will skip the download silently — the existing `--frozen-lockfile` path is exercised end-to-end with no behavioral change for non-manifest PRs - Once merged, the next manifest-touching PR (PAPA-530's #7581) will exercise the artifact path and is expected to go green ## Risks - **Behavior on non-manifest PRs:** the download step uses `continue-on-error: true` so a missing artifact is silently ignored; net behavior on PRs that don't change manifests is identical to today - **Concurrency / cross-PR artifact bleed:** artifacts are scoped to a single workflow run, so two concurrent PRs can't see each other's `pr-lockfile` - **`canary_dry_run`:** the prior unconditional `git checkout -- pnpm-lock.yaml` was a defensive no-op when `--frozen-lockfile` is honored; the new conditional preserves that for non-manifest PRs and additionally preserves the artifact-restored lockfile for manifest PRs ## Model Used - Claude (Anthropic) — `claude-opus-4-7`, extended thinking, tool use enabled ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [ ] I have run tests locally and they pass - [ ] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --- .github/workflows/pr.yml | 74 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d3df1317..48269846 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,6 +13,8 @@ jobs: policy: runs-on: ubuntu-latest timeout-minutes: 5 + outputs: + lockfile_regenerated: ${{ steps.regen_lockfile.outputs.regenerated }} steps: - name: Checkout repository @@ -63,13 +65,30 @@ jobs: node ./scripts/check-release-package-bootstrap.mjs "${changed_paths[@]}" - name: Validate dependency resolution when manifests change + id: regen_lockfile run: | changed="$(git diff --name-only "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}")" manifest_pattern='(^|/)package\.json$|^pnpm-workspace\.yaml$|^\.npmrc$|^pnpmfile\.(cjs|js|mjs)$' if printf '%s\n' "$changed" | grep -Eq "$manifest_pattern"; then pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile + echo "regenerated=1" >> "$GITHUB_OUTPUT" + else + echo "regenerated=0" >> "$GITHUB_OUTPUT" fi + # Manifest-only PRs (where pnpm-lock.yaml stays at base because the policy + # job above blocks committing it) need the regenerated lockfile for the + # downstream `pnpm install --frozen-lockfile` steps. Upload it here so + # every job consumes the same hash without recomputing. + - name: Upload regenerated lockfile for downstream jobs + if: steps.regen_lockfile.outputs.regenerated == '1' + uses: actions/upload-artifact@v4 + with: + name: pr-lockfile + path: pnpm-lock.yaml + retention-days: 1 + if-no-files-found: error + typecheck_release_registry: name: Typecheck + Release Registry needs: [policy] @@ -85,6 +104,13 @@ jobs: with: version: 9.15.4 + - name: Restore regenerated PR lockfile (if policy uploaded one) + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-lockfile + path: . + - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -125,6 +151,13 @@ jobs: with: version: 9.15.4 + - name: Restore regenerated PR lockfile (if policy uploaded one) + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-lockfile + path: . + - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -171,6 +204,13 @@ jobs: with: version: 9.15.4 + - name: Restore regenerated PR lockfile (if policy uploaded one) + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-lockfile + path: . + - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -214,6 +254,13 @@ jobs: with: version: 9.15.4 + - name: Restore regenerated PR lockfile (if policy uploaded one) + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-lockfile + path: . + - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -241,6 +288,13 @@ jobs: with: version: 9.15.4 + - name: Restore regenerated PR lockfile (if policy uploaded one) + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-lockfile + path: . + - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -251,11 +305,20 @@ jobs: run: pnpm install --frozen-lockfile # `release.sh` always executes its Step 2/7 workspace build, even when - # `--skip-verify` bypasses the initial verification gate. + # `--skip-verify` bypasses the initial verification gate. release.sh + # also requires a clean working tree, so any in-place lockfile churn + # from `pnpm install --frozen-lockfile` must be reverted first — unless + # the policy job uploaded a regenerated lockfile (manifest-only PRs), + # in which case we keep the artifact-restored copy so the workspace + # build sees a lockfile that matches the manifest. - name: Release canary dry run via release.sh internal build + env: + USED_ARTIFACT_LOCKFILE: ${{ needs.policy.outputs.lockfile_regenerated || '0' }} run: | git checkout -B master HEAD - git checkout -- pnpm-lock.yaml + if [ "$USED_ARTIFACT_LOCKFILE" != "1" ]; then + git checkout -- pnpm-lock.yaml + fi ./scripts/release.sh canary --skip-verify --dry-run e2e: @@ -272,6 +335,13 @@ jobs: with: version: 9.15.4 + - name: Restore regenerated PR lockfile (if policy uploaded one) + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: pr-lockfile + path: . + - name: Setup Node.js uses: actions/setup-node@v6 with: