From 345c7aea7ddb8c96a2ad8208ffed0f01d872e449 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 23 Aug 2026 23:03:21 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20ci:=20Gate=20Frontend=20Jest=20o?= =?UTF-8?q?n=20Codegraph=20Selection=20(Stage=201.5)=20(#15145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎨 ci: Gate Frontend Jest on Codegraph Selection (Stage 1.5) * ci: a malformed FILES decision runs FULL, never skips (Codex) * ci: dev-push runs never cancel each other (Codex P2) * ci: workflow-file push baseline, cancellable gated jobs, pull-requests read (Codex r4) * ci: selected paths must live under their workspace, else FULL (Codex r5) * ci: drop stale selected paths, run FULL when none exist (Codex r6) --- .github/workflows/backend-review.yml | 99 ++++++++++++--- .github/workflows/frontend-review.yml | 174 +++++++++++++++++++++++++- 2 files changed, 251 insertions(+), 22 deletions(-) diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml index 00dac53dc1..8416585485 100644 --- a/.github/workflows/backend-review.yml +++ b/.github/workflows/backend-review.yml @@ -27,9 +27,15 @@ on: permissions: contents: read + pull-requests: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # PR pushes supersede each other (per-PR canceling group). Push events get a PER-COMMIT group: + # dev-push runs are the post-merge safety net and the full-run baseline, and with a shared + # canceling group closely spaced merges cancel each other's runs — observed live on 2026-08-23, + # when three consecutive dev merges cancelled the runs that would have caught #15142's red + # (Codex P2 on #15145). + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true env: @@ -198,10 +204,24 @@ jobs: mode=$(echo "$RESP" | jq -r --arg w "$ws" '.selected[$w].mode') files="" if [ "$mode" = "FILES" ]; then - files=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" --arg ig "$ignore" '.selected[$w].files // [] | map(select((test(" ") | not) and (($ig == "") or (test($ig) | not)))) | map(ltrimstr($p)) | join(" ")') - spaced=$(echo "$RESP" | jq -r --arg w "$ws" '[.selected[$w].files // [] | .[] | select(test(" "))] | length') - if [ "$spaced" != "0" ]; then mode="FULL"; files=""; fi - if [ "$mode" = "FILES" ] && [ -z "$files" ]; then mode="NONE"; fi + # Only an explicit NONE may skip. FILES with a missing/empty list is malformed and + # runs FULL (Codex P1 on #15145). A NON-empty list that the ignore regex filters to + # nothing is different and legitimately NONE: those files are exactly what this + # workspace's own jest run excludes, so full CI would not run them either. + raw_n=$(echo "$RESP" | jq -r --arg w "$ws" '.selected[$w].files // [] | length') + # Every selected path must live under the workspace: a wrong-prefixed path would + # survive ltrimstr, match nothing in the workspace cwd, and --passWithNoTests would + # turn "ran nothing" into green — a silent fail-closed (Codex P1 on #15145). + misplaced=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" '[.selected[$w].files // [] | .[] | select(startswith($p) | not)] | length') + if [ "$raw_n" = "0" ] || [ "$misplaced" != "0" ]; then + mode="FULL" + note "| $ws | malformed FILES decision ($raw_n files, $misplaced outside $ws/); running FULL |" + else + files=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" --arg ig "$ignore" '.selected[$w].files // [] | map(select((test(" ") | not) and (($ig == "") or (test($ig) | not)))) | map(ltrimstr($p)) | join(" ")') + spaced=$(echo "$RESP" | jq -r --arg w "$ws" '[.selected[$w].files // [] | .[] | select(test(" "))] | length') + if [ "$spaced" != "0" ]; then mode="FULL"; files=""; fi + if [ "$mode" = "FILES" ] && [ -z "$files" ]; then mode="NONE"; fi + fi fi if [ "$mode" = "NONE" ]; then echo "${key}_run=false" >> "$GITHUB_OUTPUT" @@ -325,7 +345,7 @@ jobs: test-api: name: 'Tests: api (shard ${{ matrix.shard }}/3)' needs: [build, codegraph_select] - if: always() && needs.build.result == 'success' && needs.codegraph_select.outputs.api_run != 'false' + if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.api_run != 'false' }} runs-on: ubuntu-latest timeout-minutes: 15 strategy: @@ -409,9 +429,24 @@ jobs: SELECTED: ${{ needs.codegraph_select.outputs.api_files }} run: | cd api + # A selected path can be stale in exactly two ways at this checkout (Codex P2, #15145 r6): + # deleted on the branch — dropped, which matches full CI (the file runs nowhere) — or + # renamed, where the NEW path is a changed test file and is selected independently. If + # NOTHING selected exists, the selection is stale wholesale and the suite runs FULL; + # --passWithNoTests must never turn "ran nothing" into green. if [ -n "$SELECTED" ]; then - echo "codegraph: $(echo $SELECTED | wc -w) selected test files (safe mode)" - npm run test:ci -- --shard=${{ matrix.shard }}/3 --passWithNoTests --runTestsByPath $SELECTED + KEEP="" + for f in $SELECTED; do + if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi + done + KEEP="${KEEP# }" + if [ -z "$KEEP" ]; then + echo "no selected test file exists at HEAD (stale selection); running FULL" + npm run test:ci -- --shard=${{ matrix.shard }}/3 + else + echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" + npm run test:ci -- --shard=${{ matrix.shard }}/3 --passWithNoTests --runTestsByPath $KEEP + fi else npm run test:ci -- --shard=${{ matrix.shard }}/3 fi @@ -419,7 +454,7 @@ jobs: test-data-provider: name: 'Tests: data-provider' needs: [build, codegraph_select] - if: always() && needs.build.result == 'success' && needs.codegraph_select.outputs.dataprovider_run != 'false' + if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.dataprovider_run != 'false' }} runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -471,8 +506,18 @@ jobs: run: | cd packages/data-provider if [ -n "$SELECTED" ]; then - echo "codegraph: $(echo $SELECTED | wc -w) selected test files (safe mode)" - npm run test:ci -- --passWithNoTests --runTestsByPath $SELECTED + KEEP="" + for f in $SELECTED; do + if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi + done + KEEP="${KEEP# }" + if [ -z "$KEEP" ]; then + echo "no selected test file exists at HEAD (stale selection); running FULL" + npm run test:ci + else + echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" + npm run test:ci -- --passWithNoTests --runTestsByPath $KEEP + fi else npm run test:ci fi @@ -480,7 +525,7 @@ jobs: test-data-schemas: name: 'Tests: data-schemas' needs: [build, codegraph_select] - if: always() && needs.build.result == 'success' && needs.codegraph_select.outputs.dataschemas_run != 'false' + if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.dataschemas_run != 'false' }} runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -538,8 +583,18 @@ jobs: run: | cd packages/data-schemas if [ -n "$SELECTED" ]; then - echo "codegraph: $(echo $SELECTED | wc -w) selected test files (safe mode)" - npm run test:ci -- --passWithNoTests --runTestsByPath $SELECTED + KEEP="" + for f in $SELECTED; do + if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi + done + KEEP="${KEEP# }" + if [ -z "$KEEP" ]; then + echo "no selected test file exists at HEAD (stale selection); running FULL" + npm run test:ci + else + echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" + npm run test:ci -- --passWithNoTests --runTestsByPath $KEEP + fi else npm run test:ci fi @@ -547,7 +602,7 @@ jobs: test-packages-api: name: 'Tests: @librechat/api (shard ${{ matrix.shard }}/4)' needs: [build, codegraph_select] - if: always() && needs.build.result == 'success' && needs.codegraph_select.outputs.pkgapi_run != 'false' + if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.pkgapi_run != 'false' }} runs-on: ubuntu-latest # Suite typically completes in ~5 min on a warm runner, but tail-latency # cancellations have started showing up: tests are actively passing right @@ -619,8 +674,18 @@ jobs: run: | cd packages/api if [ -n "$SELECTED" ]; then - echo "codegraph: $(echo $SELECTED | wc -w) selected test files (safe mode)" - npm run test:ci -- --shard=${{ matrix.shard }}/4 --passWithNoTests --runTestsByPath $SELECTED + KEEP="" + for f in $SELECTED; do + if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi + done + KEEP="${KEEP# }" + if [ -z "$KEEP" ]; then + echo "no selected test file exists at HEAD (stale selection); running FULL" + npm run test:ci -- --shard=${{ matrix.shard }}/4 + else + echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" + npm run test:ci -- --shard=${{ matrix.shard }}/4 --passWithNoTests --runTestsByPath $KEEP + fi else npm run test:ci -- --shard=${{ matrix.shard }}/4 fi diff --git a/.github/workflows/frontend-review.yml b/.github/workflows/frontend-review.yml index 2f336dc8f2..362b5e3dff 100644 --- a/.github/workflows/frontend-review.yml +++ b/.github/workflows/frontend-review.yml @@ -10,18 +10,135 @@ on: - 'package-lock.json' - '.github/workflows/frontend-review.yml' - '!**.md' + # Post-merge safety net and full-run baseline for gated selection (same rationale as + # backend-review.yml stage 1): every dev merge touching frontend paths runs the full suite. + push: + branches: + - dev + paths: + - 'client/**' + - 'packages/client/**' + - 'packages/data-provider/**' + - 'package.json' + - 'package-lock.json' + - '.github/workflows/frontend-review.yml' permissions: contents: read + pull-requests: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # PR pushes supersede each other (per-PR canceling group). Push events get a PER-COMMIT group: + # dev-push runs are the post-merge safety net and the full-run baseline, and with a shared + # canceling group closely spaced merges cancel each other's runs — observed live on 2026-08-23, + # when three consecutive dev merges cancelled the runs that would have caught #15142's red + # (Codex P2 on #15145). + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true env: NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' jobs: + # Stage 1.5 of codegraph gating (stage 1 = backend jest, #15132; stage 2 = matrix lanes, + # #15136). Same mechanism, same record: across 604 finalized shadow receipts the frontend + # selection has zero structural misses (its one raw MISSED was a chronic flake), over 500 + # narrowed decisions. Selected (safe mode) on synchronize only; full on PR open/reopen, on + # every dev push, and on any doubt. A workspace skips ONLY on an explicit NONE. Kill switch: + # repo variable CODEGRAPH_GATING=off. Neither frontend workspace defines + # testPathIgnorePatterns, so --runTestsByPath needs no exclude mirroring here. + codegraph_select: + name: Codegraph select + runs-on: ubuntu-latest + timeout-minutes: 5 + if: >- + github.event_name == 'pull_request' && + github.event.action == 'synchronize' && + vars.CODEGRAPH_GATING != 'off' + outputs: + decided: ${{ steps.sel.outputs.decided }} + client_run: ${{ steps.sel.outputs.client_run }} + client_files: ${{ steps.sel.outputs.client_files }} + clientpkg_run: ${{ steps.sel.outputs.clientpkg_run }} + clientpkg_files: ${{ steps.sel.outputs.clientpkg_files }} + steps: + - name: Select tests, fail open on any doubt + id: sel + env: + URL: ${{ secrets.CODEGRAPH_URL }} + TOKEN: ${{ secrets.CODEGRAPH_TOKEN }} + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + CHANGED: ${{ github.event.pull_request.changed_files }} + run: | + set +e + note() { echo "$1" >> "$GITHUB_STEP_SUMMARY"; } + note "### Codegraph select — GATING (frontend jest)" + if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_no codegraph config; running FULL_"; exit 0; fi + if ! gh api "repos/$REPO/pulls/$PR/files" --paginate \ + --jq '.[] | {path: .filename, status, patch}' > files.ndjson; then + note "_could not fetch changed files; running FULL_"; exit 0 + fi + jq -s . files.ndjson > files.json + N=$(jq 'length' files.json) + if [ "$N" -eq 0 ] || { [ -n "$CHANGED" ] && [ "$N" -ne "$CHANGED" ]; }; then + note "_changed-file list incomplete ($N of ${CHANGED:-?}); running FULL_"; exit 0 + fi + jq -c --arg b "$BASE_SHA" --arg h "$HEAD_SHA" \ + '{files: ., mode: "safe", lockBaseSha: $b, lockHeadSha: $h}' files.json > body.json + RESP=$(curl -sS --fail-with-body -m 45 -H "Authorization: Bearer $TOKEN" \ + -H 'content-type: application/json' --data-binary @body.json "$URL/v1/select"); RC=$? + if [ "$RC" -ne 0 ] || [ -z "$RESP" ] || ! echo "$RESP" | jq -e '.selected.client.mode' >/dev/null 2>&1; then + note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); running FULL_" + exit 0 + fi + emit() { + key="$1"; ws="$2" + mode=$(echo "$RESP" | jq -r --arg w "$ws" '.selected[$w].mode') + files="" + if [ "$mode" = "FILES" ]; then + # Only an explicit NONE may skip. FILES with a missing/empty list is a malformed + # decision (service/schema skew) and must run FULL (Codex P1 on #15145). + raw_n=$(echo "$RESP" | jq -r --arg w "$ws" '.selected[$w].files // [] | length') + # Every selected path must live under the workspace: a wrong-prefixed path would + # survive ltrimstr, match nothing in the workspace cwd, and --passWithNoTests would + # turn "ran nothing" into green — a silent fail-closed (Codex P1 on #15145). + misplaced=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" '[.selected[$w].files // [] | .[] | select(startswith($p) | not)] | length') + if [ "$raw_n" = "0" ] || [ "$misplaced" != "0" ]; then + mode="FULL" + note "| $ws | malformed FILES decision ($raw_n files, $misplaced outside $ws/); running FULL |" + else + files=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" \ + '.selected[$w].files // [] | map(select(test(" ") | not)) | map(ltrimstr($p)) | join(" ")') + spaced=$(echo "$RESP" | jq -r --arg w "$ws" '[.selected[$w].files // [] | .[] | select(test(" "))] | length') + if [ "$spaced" != "0" ]; then mode="FULL"; files=""; fi + fi + fi + if [ "$mode" = "NONE" ]; then + echo "${key}_run=false" >> "$GITHUB_OUTPUT" + note "| $ws | skip (no reachable tests) |" + elif [ "$mode" = "FILES" ]; then + n=$(echo "$files" | wc -w | tr -d ' ') + echo "${key}_run=true" >> "$GITHUB_OUTPUT" + echo "${key}_files=$files" >> "$GITHUB_OUTPUT" + note "| $ws | $n selected files |" + else + echo "${key}_run=true" >> "$GITHUB_OUTPUT" + note "| $ws | FULL |" + fi + } + note "| workspace | decision |" + note "|---|---|" + emit client client + emit clientpkg packages/client + echo "decided=true" >> "$GITHUB_OUTPUT" + note "" + note "kill switch: repo variable \`CODEGRAPH_GATING=off\`; full runs remain on PR open and on every dev push" + exit 0 + build: name: Build packages runs-on: ubuntu-latest @@ -131,7 +248,10 @@ jobs: test-packages-client: name: 'Tests: @librechat/client' - needs: build + needs: [build, codegraph_select] + if: >- + !cancelled() && needs.build.result == 'success' && + needs.codegraph_select.outputs.clientpkg_run != 'false' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -164,12 +284,38 @@ jobs: path: packages/data-provider/dist - name: Run unit tests - run: npm run test:ci + env: + SELECTED: ${{ needs.codegraph_select.outputs.clientpkg_files }} + run: | + # A selected path can be stale in exactly two ways at this checkout (Codex P2, #15145 r6): + # deleted on the branch — dropped, which matches full CI (the file runs nowhere) — or + # renamed, where the NEW path is a changed test file and is selected independently. If + # NOTHING selected exists, the selection is stale wholesale and the suite runs FULL; + # --passWithNoTests must never turn "ran nothing" into green. + if [ -n "$SELECTED" ]; then + KEEP="" + for f in $SELECTED; do + if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi + done + KEEP="${KEEP# }" + if [ -z "$KEEP" ]; then + echo "no selected test file exists at HEAD (stale selection); running FULL" + npm run test:ci + else + echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" + npm run test:ci -- --passWithNoTests --runTestsByPath $KEEP + fi + else + npm run test:ci + fi working-directory: packages/client test-ubuntu: name: 'Tests: Ubuntu (shard ${{ matrix.shard }}/2)' - needs: build + needs: [build, codegraph_select] + if: >- + !cancelled() && needs.build.result == 'success' && + needs.codegraph_select.outputs.client_run != 'false' runs-on: ubuntu-latest timeout-minutes: 15 strategy: @@ -212,7 +358,25 @@ jobs: path: packages/client/dist - name: Run unit tests (shard ${{ matrix.shard }}/2) - run: npm run test:ci -- --shard=${{ matrix.shard }}/2 + env: + SELECTED: ${{ needs.codegraph_select.outputs.client_files }} + run: | + if [ -n "$SELECTED" ]; then + KEEP="" + for f in $SELECTED; do + if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi + done + KEEP="${KEEP# }" + if [ -z "$KEEP" ]; then + echo "no selected test file exists at HEAD (stale selection); running FULL" + npm run test:ci -- --shard=${{ matrix.shard }}/2 + else + echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" + npm run test:ci -- --shard=${{ matrix.shard }}/2 --passWithNoTests --runTestsByPath $KEEP + fi + else + npm run test:ci -- --shard=${{ matrix.shard }}/2 + fi working-directory: client build-verify: