🧲 fix: Match Directory Users to Existing Principals #17670
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Backend Unit Tests | |
| on: | |
| # push-to-dev runs are the post-merge safety net: gating only ever narrows pull_request | |
| # synchronize runs, so every merged state still gets the full suite — which is also what | |
| # keeps ground-truth recall telemetry alive for the codegraph shadow evaluator once | |
| # selection hides skipped tests from PR runs. | |
| push: | |
| branches: | |
| - dev | |
| paths: | |
| - 'api/**' | |
| - 'packages/**' | |
| - 'package.json' | |
| - 'package-lock.json' | |
| - 'config/circular-deps.mjs' | |
| - '.github/workflows/backend-review.yml' | |
| - '!**.md' | |
| pull_request: | |
| paths: | |
| - 'api/**' | |
| - 'packages/**' | |
| - 'package.json' | |
| - 'package-lock.json' | |
| - 'config/circular-deps.mjs' | |
| - '.github/workflows/backend-review.yml' | |
| - '!**.md' | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| concurrency: | |
| # 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_ENV: CI | |
| NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' | |
| jobs: | |
| build: | |
| name: Build packages | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Restore data-provider build cache | |
| id: cache-data-provider | |
| uses: actions/cache@v5 | |
| with: | |
| path: packages/data-provider/dist | |
| key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} | |
| - name: Build data-provider | |
| if: steps.cache-data-provider.outputs.cache-hit != 'true' | |
| run: npm run build:data-provider | |
| - name: Restore data-schemas build cache | |
| id: cache-data-schemas | |
| uses: actions/cache@v5 | |
| with: | |
| path: packages/data-schemas/dist | |
| key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} | |
| - name: Build data-schemas | |
| if: steps.cache-data-schemas.outputs.cache-hit != 'true' | |
| run: npm run build:data-schemas | |
| - name: Restore api build cache | |
| id: cache-api | |
| uses: actions/cache@v5 | |
| with: | |
| path: packages/api/dist | |
| key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }} | |
| - name: Build api | |
| if: steps.cache-api.outputs.cache-hit != 'true' | |
| run: npm run build:api | |
| - name: Upload data-provider build | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| retention-days: 2 | |
| - name: Upload data-schemas build | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: build-data-schemas | |
| path: packages/data-schemas/dist | |
| retention-days: 2 | |
| - name: Upload api build | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: build-api | |
| path: packages/api/dist | |
| retention-days: 2 | |
| # Codegraph test selection — the GATE (stage 1: backend jest only). | |
| # | |
| # Paranoia policy: FULL on opened/reopened PRs and on every push to dev (see the push | |
| # trigger above); SELECTED only on pull_request synchronize. Fork PRs carry no secrets, so | |
| # the curl fails and everything falls back to FULL. Kill switch: set repo variable | |
| # CODEGRAPH_GATING=off and this job skips, which makes every output empty and every test | |
| # job behave exactly as before this workflow change. The server itself fails open (stale | |
| # graph, unclassifiable change, root/lockfile floors => mode FULL per workspace), and this | |
| # job emits nothing unless the response parses end to end — the worst case at every layer | |
| # is "CI runs everything", which is the pre-gating behavior. | |
| 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 }} | |
| api_run: ${{ steps.sel.outputs.api_run }} | |
| api_files: ${{ steps.sel.outputs.api_files }} | |
| pkgapi_run: ${{ steps.sel.outputs.pkgapi_run }} | |
| pkgapi_files: ${{ steps.sel.outputs.pkgapi_files }} | |
| dataprovider_run: ${{ steps.sel.outputs.dataprovider_run }} | |
| dataprovider_files: ${{ steps.sel.outputs.dataprovider_files }} | |
| dataschemas_run: ${{ steps.sel.outputs.dataschemas_run }} | |
| dataschemas_files: ${{ steps.sel.outputs.dataschemas_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 (backend jest)" | |
| if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_no codegraph config; running FULL_"; exit 0; fi | |
| # A failed or truncated page must not become a shorter file list: the pipeline would hide | |
| # gh's exit status behind jq, and a partial list can turn a required lane off. Check the | |
| # fetch status AND the count against the PR's own changed_files (Codex P1, #15136). | |
| 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 | |
| # curl's status is checked explicitly: a transfer that times out or truncates after a | |
| # parseable body must fail open, not be honoured (Codex P1, #15136). --fail-with-body | |
| # also turns HTTP errors into a failure while keeping the error text for the summary. | |
| 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.api.mode' >/dev/null 2>&1; then | |
| note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); running FULL_" | |
| exit 0 | |
| fi | |
| # Emit per-workspace run flag + workspace-relative file list. A workspace emits | |
| # run=false ONLY on an explicit NONE; FULL and FILES both run (FILES filtered). | |
| # Any selected path containing a space forces that workspace FULL (paths are | |
| # server-validated to exclude quotes/backslashes/control chars, so plain | |
| # interpolation into the test command below is safe; spaces are the one shape | |
| # that would split — refuse to filter rather than risk it). | |
| emit() { | |
| key="$1"; ws="$2"; ignore="$3" | |
| 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 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" | |
| 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 "|---|---|" | |
| # Ignore regexes mirror what each workspace's own jest run excludes, because | |
| # --runTestsByPath BYPASSES testPathIgnorePatterns (verified empirically) — without | |
| # this, selection would newly run integration/manual/misc suites that full CI skips. | |
| # Character classes instead of backslashes: these strings cross YAML->bash->jq and | |
| # every escape layer is a chance to ship a filter that silently matches nothing. | |
| emit api api "" | |
| emit pkgapi packages/api 'integration|helper|__tests__/helpers/|manual[.]spec[.]' | |
| emit dataprovider packages/data-provider "" | |
| emit dataschemas packages/data-schemas 'misc/|dist/|node_modules/' | |
| 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 | |
| typecheck: | |
| name: TypeScript type checks | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download data-schemas build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-schemas | |
| path: packages/data-schemas/dist | |
| - name: Download api build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-api | |
| path: packages/api/dist | |
| - name: Type check data-provider | |
| run: npx tsc --noEmit -p packages/data-provider/tsconfig.json | |
| - name: Type check data-schemas | |
| run: npx tsc --noEmit -p packages/data-schemas/tsconfig.json | |
| - name: Type check @librechat/api | |
| run: npx tsc --noEmit -p packages/api/tsconfig.json | |
| - name: Type check @librechat/client | |
| run: npx tsc --noEmit -p packages/client/tsconfig.json | |
| circular-deps: | |
| name: Circular dependency checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Detect circular dependencies | |
| run: node config/circular-deps.mjs | |
| test-api: | |
| name: 'Tests: api (shard ${{ matrix.shard }}/3)' | |
| needs: [build, codegraph_select] | |
| if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.api_run != 'false' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3] | |
| env: | |
| MONGO_URI: ${{ secrets.MONGO_URI }} | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| JWT_SECRET: ${{ secrets.JWT_SECRET }} | |
| CREDS_KEY: ${{ secrets.CREDS_KEY }} | |
| CREDS_IV: ${{ secrets.CREDS_IV }} | |
| BAN_VIOLATIONS: ${{ secrets.BAN_VIOLATIONS }} | |
| BAN_DURATION: ${{ secrets.BAN_DURATION }} | |
| BAN_INTERVAL: ${{ secrets.BAN_INTERVAL }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download data-schemas build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-schemas | |
| path: packages/data-schemas/dist | |
| - name: Download api build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-api | |
| path: packages/api/dist | |
| - name: Create empty auth.json file | |
| run: | | |
| mkdir -p api/data | |
| echo '{}' > api/data/auth.json | |
| - name: Prepare .env.test file | |
| run: cp api/test/.env.test.example api/test/.env.test | |
| # mongodb-memory-server cold-downloads a ~122MB MongoDB binary into | |
| # ~/.cache/mongodb-binaries on first use — inside a 15s beforeAll hook, which is a | |
| # timeout on a slow mirror day. Every MISSED verdict the codegraph shadow evaluator has | |
| # ever recorded (9 across 6 PRs) plus several chronically flaky suites trace to exactly | |
| # this download. restore-keys keeps the previous binary warm across lockfile churn; a | |
| # genuinely new binary version downloads once and re-saves. | |
| - name: Cache MongoDB memory-server binaries | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/mongodb-binaries | |
| key: mongodb-binaries-${{ runner.os }}-${{ hashFiles('package-lock.json') }} | |
| restore-keys: | | |
| mongodb-binaries-${{ runner.os }}- | |
| - name: Run unit tests (shard ${{ matrix.shard }}/3) | |
| env: | |
| 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 | |
| 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 | |
| test-data-provider: | |
| name: 'Tests: data-provider' | |
| needs: [build, codegraph_select] | |
| if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.dataprovider_run != 'false' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| # mongodb-memory-server cold-downloads a ~122MB MongoDB binary into | |
| # ~/.cache/mongodb-binaries on first use — inside a 15s beforeAll hook, which is a | |
| # timeout on a slow mirror day. Every MISSED verdict the codegraph shadow evaluator has | |
| # ever recorded (9 across 6 PRs) plus several chronically flaky suites trace to exactly | |
| # this download. restore-keys keeps the previous binary warm across lockfile churn; a | |
| # genuinely new binary version downloads once and re-saves. | |
| - name: Cache MongoDB memory-server binaries | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/mongodb-binaries | |
| key: mongodb-binaries-${{ runner.os }}-${{ hashFiles('package-lock.json') }} | |
| restore-keys: | | |
| mongodb-binaries-${{ runner.os }}- | |
| - name: Run unit tests | |
| env: | |
| SELECTED: ${{ needs.codegraph_select.outputs.dataprovider_files }} | |
| run: | | |
| cd packages/data-provider | |
| 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 | |
| test-data-schemas: | |
| name: 'Tests: data-schemas' | |
| needs: [build, codegraph_select] | |
| if: ${{ !cancelled() && needs.build.result == 'success' && needs.codegraph_select.outputs.dataschemas_run != 'false' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download data-schemas build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-schemas | |
| path: packages/data-schemas/dist | |
| # mongodb-memory-server cold-downloads a ~122MB MongoDB binary into | |
| # ~/.cache/mongodb-binaries on first use — inside a 15s beforeAll hook, which is a | |
| # timeout on a slow mirror day. Every MISSED verdict the codegraph shadow evaluator has | |
| # ever recorded (9 across 6 PRs) plus several chronically flaky suites trace to exactly | |
| # this download. restore-keys keeps the previous binary warm across lockfile churn; a | |
| # genuinely new binary version downloads once and re-saves. | |
| - name: Cache MongoDB memory-server binaries | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/mongodb-binaries | |
| key: mongodb-binaries-${{ runner.os }}-${{ hashFiles('package-lock.json') }} | |
| restore-keys: | | |
| mongodb-binaries-${{ runner.os }}- | |
| - name: Run unit tests | |
| env: | |
| SELECTED: ${{ needs.codegraph_select.outputs.dataschemas_files }} | |
| run: | | |
| cd packages/data-schemas | |
| 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 | |
| test-packages-api: | |
| name: 'Tests: @librechat/api (shard ${{ matrix.shard }}/4)' | |
| needs: [build, codegraph_select] | |
| 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 | |
| # up to the timeout, then the job is killed mid-suite. Sharding splits the | |
| # suite across runners; per-shard headroom still absorbs runner variance. | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| api/node_modules | |
| packages/api/node_modules | |
| packages/data-provider/node_modules | |
| packages/data-schemas/node_modules | |
| key: node-modules-backend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download data-schemas build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-schemas | |
| path: packages/data-schemas/dist | |
| - name: Download api build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-api | |
| path: packages/api/dist | |
| # mongodb-memory-server cold-downloads a ~122MB MongoDB binary into | |
| # ~/.cache/mongodb-binaries on first use — inside a 15s beforeAll hook, which is a | |
| # timeout on a slow mirror day. Every MISSED verdict the codegraph shadow evaluator has | |
| # ever recorded (9 across 6 PRs) plus several chronically flaky suites trace to exactly | |
| # this download. restore-keys keeps the previous binary warm across lockfile churn; a | |
| # genuinely new binary version downloads once and re-saves. | |
| - name: Cache MongoDB memory-server binaries | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/mongodb-binaries | |
| key: mongodb-binaries-${{ runner.os }}-${{ hashFiles('package-lock.json') }} | |
| restore-keys: | | |
| mongodb-binaries-${{ runner.os }}- | |
| - name: Run unit tests (shard ${{ matrix.shard }}/4) | |
| env: | |
| SELECTED: ${{ needs.codegraph_select.outputs.pkgapi_files }} | |
| run: | | |
| cd packages/api | |
| 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 }}/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 |