LibreChat/.github/workflows
Danny Avila d369c649ed
🪥 chore: Run CI's Static Checks on Each Commit's Diff (#15303)
* 🪝 chore: Run Static Checks on Every Commit

Adds `scripts/static-checks.mts`, a local port of the Static Checks CI job
(.github/workflows/static-checks.yml) scoped to the files in a diff. It
resolves the changed-file list, applies the same `dorny/paths-filter` groups
the job uses, and runs whichever checks those paths activate — ESLint,
Prettier, import order, ESLint config validation, package.json validation, and
(behind `--full`) config migration tests, unused i18n keys and depcheck. Like
the job, every selected check runs even after one fails and the failures are
summarized at the end.

The pre-commit hook keeps lint-staged for the per-file layer, which verifies
the exact staged content of partially staged files, then runs the script for
everything lint-staged cannot cover. lint-staged now uses the job's ESLint
invocation, so warnings fail locally the way they fail CI.

The slow gates stay opt-in (`npm run static-checks:full`, or
`STATIC_CHECKS_FULL=1`) to keep commit latency unchanged.

Hooks were never installed: `config/prepare.js` existed but no `prepare` script
called it, so the hook only ran where `core.hooksPath` had been set by hand.
Replaces it with an inline `prepare` — both Dockerfiles run `npm ci` after
copying only the manifests, so a `node config/prepare.js` step would fail the
image build, and husky is absent from `--omit=dev` installs.

The i18n scan is a single pass over the source identifiers rather than one grep
per key, verified to flag exactly the same keys as the CI loop (including the
substring and dynamic-key cases) in 0.5s instead of 14s.

* 🩹 fix: Address Codex Round 1 on the Static Checks Runner

Activate gates from the unfiltered changed-path list. `dorny/paths-filter`
matches deleted paths too, so gating on the `--diff-filter=ACMRTUXB` list the
per-file steps use let a delete-only commit — the last reference to a
translation key, say — slip past the i18n and depcheck gates. The two lists are
now derived separately, the way CI derives them.

Pass `-m` to `git diff-tree` in `--commit` mode. Without it a merge commit
emits no paths at all, so `--commit <merge-sha>` reported "Nothing to check";
a real merge in this repo's history goes from 0 to 97 files.

Build the workspaces the config suite imports instead of skipping when `dist`
is absent. The three `dist` directories are gitignored and `npm ci` does not
produce them, so a fresh checkout reported a pass for a gate that never ran —
and an existing `dist` could be stale. Each is a sub-second tsdown build.

Resolve a global depcheck through a shell on Windows, where npm exposes it as
`depcheck.cmd` and `spawnSync` cannot see the shim.

Skip dot directories when walking for imports. `.claude/worktrees/` can hold a
full checkout per branch — 117 on this machine — and the root-wide scan behind
the depcheck gate walked every one of them.

Records the remaining boundary in the header: the per-file checks see exact
staged content via lint-staged, while the tree-wide gates read the working
tree, as running them by hand would.

* 🩹 fix: Address Codex Round 2 on the Static Checks Runner

Treat an unresolvable checker as a failure. ESLint and Prettier missing meant
the runner printed "All affected static checks passed" without having linted
anything; only depcheck, which CI installs globally and this documents as
optional, may still skip.

Catch per-check exceptions. The runner promises that every selected check runs
even after a failure, but a throw — a malformed translation JSON, say —
escaped and cancelled the checks after it. Each is now recorded as that
check's failure; verified that depcheck still runs after i18n throws.

Restrict `--commit` to the checked-out commit. Paths came from the named
commit while contents came from the working tree, so an older revision was
scored against the wrong file contents: a file added then deleted vanished,
and one modified since was read at its newer contents. It now fails with a
pointer to `--against`.

Reject unknown options. `--ful` silently ran the fast tier and `--commmit HEAD`
treated `HEAD` as a file path, both exiting 0 and implying gates had run.

Cover the runner in CI. `scripts/**` was absent from the workflow's trigger
paths, so a PR touching only the script the pre-commit hook now depends on got
no Static Checks run — and ESLint has no flat-config match for
`scripts/**/*.mts`, so nothing else loads it either. Adds the trigger path, a
`runner` filter group and a step that runs the script against the PR's own diff.

* 🔗 feat: Add Circular Dependency and TypeScript Gates

Both already run in CI as jobs of the Backend Unit Tests workflow; this brings
them to the local runner so they land before a push rather than after.

Circular dependencies (`node config/circular-deps.mjs`) is fast enough at 0.9s
to sit in the per-commit tier, gated on the same paths that trigger the CI job.

TypeScript stays opt-in behind `--full`: the five projects cost between 2.3s
and 20.9s each, which is too much per commit. Each project declares the paths
that can affect it — its own sources plus its upstream packages — so an edit to
data-provider still typechecks data-schemas, api, packages/client and client,
while an `api/**`-only change runs none of them, since no typechecked project
includes that directory. The builds a project's imports resolve through are
made first, mirroring the CI jobs' dependency on the build artifacts, through a
helper the config suite now shares.

Also addresses codex round 3:

Reject conflicting target selectors. `--against origin/dev package.json`
silently checked only the file, and `--against <bad-ref> --commit HEAD` never
resolved the bad base, so a caller could believe a range had been checked.

Require a clean worktree in `--commit` mode. The HEAD-only restriction was not
enough: contents still come from the working tree, so an uncommitted edit was
scored against the named commit — an invalid uncommitted package.json failing a
valid HEAD, or an uncommitted fix masking a defect in it.

The summary now names how many checks were skipped rather than reporting a
bare pass, and a typecheck failure carries the stale-workspace-build hint —
inside a git worktree `librechat-data-provider` resolves to the main checkout,
whose dist can predate the branch and shows up as missing properties.

* 🩹 fix: Address Codex Round 4 on the Static Checks Runner

Diff `--against` from the merge base. A two-dot diff reports the base branch's
own commits in reverse once it advances, so `--against origin/dev` scored 64
files for a branch that changed 7, activating gates for files the branch never
touched. Three dots makes the documented PR-style command mean what it says.

Typecheck on root manifest changes. Both review workflows trigger their
TypeScript jobs on package.json and package-lock.json, because a dependency or
@types bump breaks compilation on its own; the local filter ignored them, so
`static-checks:full` passed where CI would fail.

Validate every workspace manifest. The list mirrored the four the CI step
happens to name, so a malformed packages/api, data-provider or data-schemas
manifest passed validation in the revision modes, which have no lint-staged
pass behind them. Both lists now cover all seven.

Make the runner smoke execute a check. `--list` never runs one, and a
script-only PR activates no group, so the CI coverage added for exactly that
case could pass with the execution path untouched. It now runs against an
explicit target.

* 🩹 fix: Address Codex Round 5 on the Static Checks Runner

Activate the JSON gate for every manifest it validates. Round 4 added the four
workspace manifests to the validation list but not to the filter that turns the
gate on, so a malformed packages/data-provider or data-schemas manifest still
passed when it was the only changed file — the list grew and the trigger did
not. The same two entries also feed the unused-package calculation, reached
through api/package.json's @librechat/data-schemas dependency.

Include the owning workflows in the imported gates' filters. Circular
dependencies and TypeScript come from the review workflows, both of which list
their own YAML in `on.paths` and therefore rerun those jobs when the workflow
changes; locally the gates stayed inactive, so a change to how they are built
or invoked could bypass the local equivalent. Added to the group filters and to
the per-project predicates, since a workflow-only change would otherwise
activate the group and then select no project.

Bound command batches by characters rather than file count. Windows caps a
command line at 32767 characters, far below POSIX ARG_MAX, and a count does not
bound that: 400 of this repository's longer paths already come to 30176
characters before the executable and fixed arguments. Verified that a list
spanning several batches still reports a defect in its final file.
2026-08-28 10:08:37 -04:00
..
a11y.yml 🚦 ci: Reduce GitHub Actions Runner Pressure (#14716) 2026-08-09 07:41:00 -04:00
agents-integration-tests.yml 🛰️ test: Prove Cross-Replica Subagent Delivery (#15064) 2026-08-21 03:36:11 -04:00
backend-review.yml 🎨 ci: Gate Frontend Jest on Codegraph Selection (Stage 1.5) (#15145) 2026-08-23 23:03:21 -04:00
build.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
cache-integration-tests.yml refactor: Presets, Skills Motion and Model Selector Polish (#14953) 2026-08-19 15:49:47 -04:00
client.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
codegraph-e2e-votes.yml 📍 ci: Pin the Votes Ledger Results JSON to an Absolute Path (#15166) 2026-08-24 10:33:12 -04:00
codegraph-select.yml 🎭 ci: Gate Playwright Lanes and Docker Smokes on Codegraph Selection (Stage 2) (#15136) 2026-08-23 14:58:45 -04:00
data-provider.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
data-schemas.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
deploy.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
dev-branch-images.yml 🧩 ci: Close Workflow Path-Filter Gaps (#14728) 2026-08-10 17:26:50 -04:00
dev-images.yml 🧩 ci: Close Workflow Path-Filter Gaps (#14728) 2026-08-10 17:26:50 -04:00
dev-staging-images.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
docker-smoke.yml 🎭 ci: Gate Playwright Lanes and Docker Smokes on Codegraph Selection (Stage 2) (#15136) 2026-08-23 14:58:45 -04:00
frontend-review.yml 🎨 ci: Gate Frontend Jest on Codegraph Selection (Stage 1.5) (#15145) 2026-08-23 23:03:21 -04:00
frontend-windows-nightly.yml 🧩 ci: Close Workflow Path-Filter Gaps (#14728) 2026-08-10 17:26:50 -04:00
helmcharts.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
langfuse-fanout.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
locize-i18n-sync.yml 📚 fix: Keep English Source Repository-Owned (#14920) 2026-08-17 02:29:34 -04:00
main-image-workflow.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00
playwright-bombadil.yml 👷 ci: Stop Optional Playwright Fonts From Failing E2E (#14852) 2026-08-14 20:35:37 -04:00
playwright-mock.yml 🎓 ci: Graduated E2E Spec Skipping, Wired Dark Until Armed (#15172) 2026-08-24 11:53:50 -04:00
retry-docker-builds.yml 🧱 ci: Retry Failed Docker Builds (#13935) 2026-06-24 10:09:36 -04:00
static-checks.yml 🪥 chore: Run CI's Static Checks on Each Commit's Diff (#15303) 2026-08-28 10:08:37 -04:00
sync-helm-chart-tags.yml 🧾 ci: Skip Workflows for Markdown-Only Changes (#14378) 2026-07-21 20:35:59 -04:00
tag-images.yml ⚙️ ci: Bump GitHub Actions to Node.js 24 Runtimes (#14689) 2026-08-07 11:25:29 -04:00