From 4ef6155aae27200c91287c6537c922e596622f8d Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Fri, 4 Sep 2026 08:58:44 -0500 Subject: [PATCH] ci: harden paid runner browser and lock repair (#12829) ## Thinking Path Paid cells now reuse the AWS image's system Chrome, but Playwright video recording still resolves its revision-pinned FFmpeg helper from the Playwright cache. Run 33875618534 proved Chrome qualification succeeds and then failed before provider startup because that helper was absent. The same run also exposed that generic lock repair can churn unrelated package platform metadata, so the automated repair paths need resolution-only regeneration rather than lockfile-only metadata refresh. ## What Changed - install Playwright FFmpeg only on the AWS/system-Chrome path - retry the small helper installation up to three times before provider secrets are exposed - keep the GitHub-hosted Chromium fallback unchanged - bind static coverage to the exact FFmpeg step block and its pre-secret ordering - add pnpm `--resolution-only` to all four automated lock-repair paths while retaining full transitive resolution - require resolution-only repair in the shared workflow regression The actual generated lockfile correction remains bot-owned by PR #12828 and is intentionally not committed here. ## Verification - `node --test .github/scripts/tests/lockfile-refresh-workflows.test.mjs` - `actionlint -ignore SC2012` on all modified workflows - focused Prettier checks - `git diff --check` - prior run 33875618534: system Chrome 151 qualified; missing Playwright FFmpeg was the sole cell startup failure ## Risks Low. The new network operation is limited to Playwright's pinned FFmpeg payload, happens before paid credentials are exposed, and leaves the hosted-runner path unchanged. Resolution-only is still a full dependency-resolution pass, unlike lockfile-only, while avoiding unrelated current-platform metadata churn. ## Model Used GPT-5 --- .../tests/lockfile-refresh-workflows.test.mjs | 1 + .github/workflows/docker.yml | 4 ++-- .github/workflows/pr-trusted.yml | 2 +- .github/workflows/refresh-lockfile.yml | 2 +- .github/workflows/runner-full-stack-e2e.yml | 15 ++++++++++++++ scripts/__tests__/e2e-shard.test.mjs | 4 ++-- tests/runner-e2e/workflow-security.test.ts | 20 +++++++++++++++++++ 7 files changed, 42 insertions(+), 6 deletions(-) diff --git a/.github/scripts/tests/lockfile-refresh-workflows.test.mjs b/.github/scripts/tests/lockfile-refresh-workflows.test.mjs index 52fef1f698..7c19cf2cb9 100644 --- a/.github/scripts/tests/lockfile-refresh-workflows.test.mjs +++ b/.github/scripts/tests/lockfile-refresh-workflows.test.mjs @@ -17,6 +17,7 @@ test('lockfile repair workflows resolve dependencies instead of updating metadat assert.ok(repairCommands.length > 0, `${workflow} must contain a lockfile repair command`); for (const command of repairCommands) { + assert.match(command, /--resolution-only/); assert.match(command, /--ignore-scripts/); assert.doesNotMatch(command, /--lockfile-only/); } diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5fb9f7e600..793568d0b5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -87,7 +87,7 @@ jobs: - name: Refresh lockfile for Docker build context run: | set -euo pipefail - pnpm install --ignore-scripts --no-frozen-lockfile + pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile changed="$(git status --porcelain)" if [ -z "$changed" ]; then @@ -281,7 +281,7 @@ jobs: - name: Refresh lockfile for Docker build context run: | set -euo pipefail - pnpm install --ignore-scripts --no-frozen-lockfile + pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile changed="$(git status --porcelain)" if [ -z "$changed" ]; then diff --git a/.github/workflows/pr-trusted.yml b/.github/workflows/pr-trusted.yml index edbe655136..cc04e18fc4 100644 --- a/.github/workflows/pr-trusted.yml +++ b/.github/workflows/pr-trusted.yml @@ -346,7 +346,7 @@ jobs: id: regen_lockfile run: | cp pnpm-lock.yaml "$RUNNER_TEMP/pnpm-lock.before.yaml" - pnpm install --ignore-scripts --no-frozen-lockfile + pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile if cmp -s "$RUNNER_TEMP/pnpm-lock.before.yaml" pnpm-lock.yaml; then echo "regenerated=0" >> "$GITHUB_OUTPUT" else diff --git a/.github/workflows/refresh-lockfile.yml b/.github/workflows/refresh-lockfile.yml index 039d21970e..df9bd7abef 100644 --- a/.github/workflows/refresh-lockfile.yml +++ b/.github/workflows/refresh-lockfile.yml @@ -35,7 +35,7 @@ jobs: cache: pnpm - name: Refresh pnpm lockfile - run: pnpm install --ignore-scripts --no-frozen-lockfile + run: pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile - name: Fail on unexpected file changes run: | diff --git a/.github/workflows/runner-full-stack-e2e.yml b/.github/workflows/runner-full-stack-e2e.yml index 9c45df4479..1150d96d54 100644 --- a/.github/workflows/runner-full-stack-e2e.yml +++ b/.github/workflows/runner-full-stack-e2e.yml @@ -923,6 +923,21 @@ jobs: test -x "$chrome_path" google-chrome --version + - name: Install Playwright FFmpeg on AWS runner + if: needs.authorize.outputs.playwright_channel == 'chrome' + run: | + set -euo pipefail + for attempt in 1 2 3; do + if pnpm exec playwright install ffmpeg; then + exit 0 + fi + if [ "$attempt" -eq 3 ]; then + echo "Playwright FFmpeg installation failed after $attempt attempts." >&2 + exit 1 + fi + sleep "$((attempt * 10))" + done + - name: Install Chromium headless shell on GitHub-hosted fallback if: needs.authorize.outputs.playwright_channel != 'chrome' run: | diff --git a/scripts/__tests__/e2e-shard.test.mjs b/scripts/__tests__/e2e-shard.test.mjs index 691c0b6cc1..ae56c9c411 100644 --- a/scripts/__tests__/e2e-shard.test.mjs +++ b/scripts/__tests__/e2e-shard.test.mjs @@ -306,8 +306,8 @@ test("the trusted PR workflow regenerates stale stacked lockfiles", () => { ); assert.match( workflow, - /pnpm install --ignore-scripts --no-frozen-lockfile/, - "the policy job must resolve the complete merge tree instead of only updating lockfile metadata", + /pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile/, + "the policy job must resolve the complete merge tree without rewriting platform metadata", ); assert.match( workflow, diff --git a/tests/runner-e2e/workflow-security.test.ts b/tests/runner-e2e/workflow-security.test.ts index ee46454dd1..93623e2d24 100644 --- a/tests/runner-e2e/workflow-security.test.ts +++ b/tests/runner-e2e/workflow-security.test.ts @@ -266,10 +266,30 @@ describe("public repository paid workflow security", () => { const daytonaPluginPreparation = paidJob.indexOf( "Prepare bundled Daytona plugin without dependency lifecycle scripts", ); + const awsFfmpegInstall = paidJob.indexOf( + "- name: Install Playwright FFmpeg on AWS runner", + ); + const hostedChromiumInstall = paidJob.indexOf( + "- name: Install Chromium headless shell on GitHub-hosted fallback", + ); const paidExecution = paidJob.indexOf("- name: Run paid cell"); expect(paidInstall).toBeGreaterThan(0); expect(daytonaPluginPreparation).toBeGreaterThan(paidInstall); + expect(awsFfmpegInstall).toBeGreaterThan(daytonaPluginPreparation); + expect(hostedChromiumInstall).toBeGreaterThan(awsFfmpegInstall); + expect(paidExecution).toBeGreaterThan(awsFfmpegInstall); expect(paidExecution).toBeGreaterThan(daytonaPluginPreparation); + const awsFfmpegStep = paidJob.slice( + awsFfmpegInstall, + hostedChromiumInstall, + ); + expect(awsFfmpegStep).toContain( + "if: needs.authorize.outputs.playwright_channel == 'chrome'", + ); + expect(awsFfmpegStep).toContain("pnpm exec playwright install ffmpeg"); + expect(awsFfmpegStep).not.toMatch( + /(?:OPENAI|ANTHROPIC|OPENROUTER|DAYTONA)_API_KEY/, + ); const preparedBeforeProviderAccess = paidJob.slice( daytonaPluginPreparation, paidExecution,