The full-sweep regression gate lints api+client+packages twice — once under
the PR's config and once under the base ref's — inside the same job as ~20
later steps (data-provider/data-schemas/api builds, config migration tests,
unused-i18n scan, and four depcheck passes), all sharing one 30-minute budget.
Two type-aware sweeps of the whole tree cost more than everything else in that
job combined. When they run long the job hits its timeout mid-sweep, so every
step behind the gate never executes and Static checks reports no result at all
— strictly worse than not running the gate. continue-on-error: true hides this,
because the step never fails; it simply never finishes.
Move the gate to its own job with its own budget so it cannot starve the other
checks, and bound each sweep so an over-budget run degrades to a notice rather
than a failure — an unfinished sweep is no evidence of a regression, and the
gate is advisory about config scope. Behaviour on a sweep that completes is
unchanged: coverage loss and new (file, rule, severity) diagnostics still fail.
* 🧩 ci: Close Workflow Path-Filter Gaps
Six trigger-filter gaps found by reading all workflows against the live
dependency graph (AI-1755, codegraph FINDINGS §6l):
- backend-review/frontend-review: root package.json/package-lock.json now
trigger unit tests — a lockfile-only dependency bump previously ran zero
backend or frontend unit tests while every test job installs from it
- agents-integration-tests: widen to the three package src trees it builds
and imports (was only src/agents/**)
- cache-integration-tests: same shape — verified live that
packages/api/src/flow/manager.ts (imported by mcp/oauth) matched neither
integration filter
- docker-smoke: plain Dockerfile had no PR-time validation despite shipping
via dev-images/tag-images; new node-image-smoke job builds it, gated by
paths-filter to Dockerfile/.dockerignore changes
- dev-images/dev-branch-images: add config/**, skill/**, .dockerignore —
the single-stage image COPYs the full build context
- static-checks: eslint.config.mjs now re-triggers the lint job (gap carried
over from eslint-ci.yml in the #14716 consolidation)
- delete generate_embeddings.yml: fired on docs/**, which no longer exists,
and its docs-root-path pointed at the same missing directory
* 🧩 ci: Address Codex Review Findings
- Build caches: all 26 build-* keys across 8 workflows now lead with
root package.json + package-lock.json so manifest-only bumps cannot
restore stale dists (data-provider embeds the root version); unifies
the split key families (playwright already hashed the lockfile)
- static-checks: config changes now gate on the ESLint config loading
and applying to representative files, plus a report-only full-tree
sweep (70 pre-existing errors at dev HEAD block a hard gate for now)
- docker-smoke: the workflow file itself triggers the plain-Dockerfile
build so job edits are validated
- dev-images/dev-branch-images: re-include skill/**/*.md after !**.md
so shipped deployment-skill Markdown rebuilds images
* 🧩 ci: Gate Config Lint Sweep on Regression vs Base Config
Second-round codex finding: the report-only sweep swallowed config-wide
breakage in scoped blocks the representative files don't exercise. The
sweep now lints the same tree under the PR's config and the base ref's
config and fails only when the PR's config produces more diagnostics for
some (file, rule) pair — pre-existing debt never fails the gate, and
fixes are never penalized. Base-config unavailability degrades to the
load gate with a notice. Outcome surfaced in the failure summary.
* 🧩 ci: Harden Config Lint Gate per External Review
- Coverage direction: fail when the PR config stops linting files the
base config covered (set difference on linted files) — a mis-scoped
ignores previously only removed diagnostics and passed both gates
- Severity-aware fingerprints: (file, rule, severity) so warn->error
escalations gate on a clean tree for that rule; downgrades still free
- Hard-fail when the base commit is missing so a future shallow-checkout
change cannot silently disable the gate; annotate fetch-depth: 0
- Tab-separated fingerprint keys (space-in-path proof), --config on
both sweeps, EXIT trap for the base config copy, comment on why it
must live at the repo root (flat-config pattern base paths)
- Narrow skill md re-include with !skill/README.md: top-level README is
documentation-only; runtime skill Markdown still rebuilds images
* ci: bump GitHub Actions to Node.js 24 runtimes
Clear Node 20 deprecation warnings on runners by moving workflow
actions to majors that declare node24 (checkout, cache, setup-node,
artifacts, Docker buildx/build-push/qemu/login, github-script,
setup-go, Azure login/helm, create-pull-request, axe-linter).
* fix: release leader lock via ioredis on Redis Cluster
@keyv/redis EVAL can surface unhandled MOVED redirects on cluster,
so resign() logged failure and left LeadingServerUUID set. Use ioredis
for leader election SET NX / GET / Lua (same pattern as principals and
concurrency locks) so cluster redirects are retried and resignation
clears the lock.
* 🍃 fix: Amazon DocumentDB Compatibility for Pipeline-Form Updates
- Rewrite acceptTerms without aggregation-pipeline update + $$NOW (null-guarded first-acceptance claim preserves the original timestamp under concurrent and repeat requests)
- Rewrite decrementTagCounts clamp-at-zero decrement as ordered two-op bulkWrite (clamp before guarded $inc)
- Rewrite extendFilesTTL TTL hold as projected read + per-doc guarded $set via tenantSafeBulkWrite, preserving only-widens/ceiling/cleared-stays-permanent semantics
- Log background index-build failures via Model 'index' listeners (previously swallowed silently, e.g. partialFilterExpression rejection on DocumentDB <5.0)
- Add misc/documentdb live-compatibility harness + assessment (AWS-cited)
- Fix latent file.spec helper bug: createdAt backdating was silently stripped by mongoose immutability
Closes#14488
* 🍃 fix: Harden DocumentDB-Safe Updates Against Cross-Call Races
Addresses Codex review on #14495:
- decrementTagCounts: normalize-null / $inc / clamp-negative op triple so
interleaved decrements of the same tag converge on max(0, ...) exactly as
the serialized pipeline did (clamp keys on count < 0, not count < amount)
- acceptTerms: guard the repeat-acceptance fallback with a non-null timestamp
(exact complement of the claim guard) and retry the claim when a
config/reset-terms.js reset races between the two updates, so acceptance
never resurrects a reset cycle without a fresh audit timestamp
* 👷 ci: Suppress Ignored-File Warnings in Changed-File ESLint Run
Changed files under config-ignored paths (packages/data-schemas/misc/**)
emit "File ignored" warnings that fail --max-warnings=0.
* 🛡️ fix: Guard HITL checkpoint size against MongoDB 16MB limit
A LangGraph HITL checkpoint embeds the whole serialized message history
in a single BSON document, so a large conversation (inlined base64 media,
big tool outputs, long history) can serialize past MongoDB's 16MB
document ceiling. `MongoDBSaver.put` would then throw a raw
`BSONObjectTooLarge` at pause time and the pause would be lost with no
legible error.
`LazyMongoSaver` now measures the serialized checkpoint on the persist
path (rare HITL pauses only — the clean-exit common path is untouched):
debug-logs the size, warns past a soft 8MB threshold, and throws a typed
`CheckpointTooLargeError` before the doomed write past a 15MB hard limit
(16MB minus headroom for the document's other fields). Thresholds are
overridable via the constructor for testing.
Adds integration coverage (real serde + mongodb-memory-server) for the
under-threshold, soft-warn, and hard-reject cases.
* 🧱 fix: Add explicit types for isolatedDeclarations build
The production build (tsdown + rolldown-plugin-dts) compiles with
--isolatedDeclarations, which requires explicit type annotations on
exported bindings whose initializers it can't infer syntactically.
`CHECKPOINT_HARD_LIMIT_BYTES` (arithmetic over two consts) tripped
TS9010; annotate it and `CHECKPOINT_WARN_BYTES` as `number`. Verified
with `tsc --isolatedDeclarations` over the package.
* fix: Codex review — include metadata in the size guard + flush parked bookkeeping
P1 (lost bookkeeping): `put` consumes the write anchor, then AWAITS
assertCheckpointFitsDocument (checkpoint serialization). A bookkeeping-only
putWrites dispatched in that window sees neither the anchor nor persistedIds,
so it parks — and `put` never flushed it, dropping the marker (e.g. a
completed Send-sibling's __no_writes__) so a resume re-executed the sibling.
Extract flushBufferedBookkeeping (shared with the anchoring putWrites) and call
it after super.put in the persist path.
P2 (metadata ignored by guard): MongoDBSaver.put stores the serialized
checkpoint AND metadata (plus metadata_search) in the SAME document, but the
guard measured only the checkpoint — a just-under-limit checkpoint with large
metadata fell through to a raw BSONObjectTooLarge. Measure checkpoint +
metadata; the fixed headroom now only covers metadata_search/ids/framing.
Two integration regressions added (both fail without the fix, pass with it):
metadata-pushes-over-the-ceiling, and flush-during-the-serialization-window.
* fix: count metadata_search (raw metadata copy) in the checkpoint size guard
Codex follow-up: MongoDBSaver.put stores metadata a SECOND time as
`metadata_search: metadata` — the whole raw metadata object as a queryable BSON
subdocument in the same agent_checkpoints document. Measuring only checkpoint +
serialized metadata undercounted by that raw copy, so a large metadata.writes
payload could pass the 15 MB preflight while metadata_search pushed the actual
BSON past 16 MB — the raw BSONObjectTooLarge the guard exists to prevent.
Add mongoose.mongo.BSON.calculateObjectSize(metadata) for the metadata_search
contribution (mongoose already imported; no new dep). Headroom now only covers
ids + BSON framing. New integration test sizes a case where checkpoint +
serialized metadata is under the limit but the raw metadata_search copy pushes
it over — green (24/24).
* ci: run packages/api agents integration specs (checkpointer) in CI
The checkpointer.integration.spec.ts (durable HITL checkpointer vs a real
in-process MongoDB) is a *.integration.spec.ts, which test:ci deliberately
excludes — and cache-integration-tests.yml only covers cache/cluster/mcp/
stream, not src/agents/**. So it ran nowhere and its regressions guarded
nothing. Add:
- test:agents-integration script (jest over src/agents/*.integration.spec.ts,
runInBand — mongodb-memory-server is in-process, no external service);
- a dedicated agents-integration-tests.yml (mirrors the proven build setup,
no Redis) triggered on packages/api/src/agents/** changes;
- babel-plugin-replace-ts-export-assignment as a packages/api devDep: the spec
imports @langchain/langgraph-checkpoint (whitelisted for babel transform,
uses `export =`), whose transform needs this plugin — it was only present
under client/node_modules, unresolvable from packages/api, so the suite
couldn't load. 24/24 pass locally.
DataTable.spec failed with "Too many re-renders" (35 tests). Root cause: @tanstack/react-virtual is measurement-driven, and jsdom has no real layout, so its re-render loop never converges. This went unnoticed because packages/client had no jest CI job (only the client workspace runs jest in frontend-review.yml).
- DataTable: only read the virtualizer (getVirtualItems/getTotalSize) when virtualization is active; the non-virtualized branch renders rows directly, so engaging it for small tables was wasted render-phase work.
- Spec: mock @tanstack/react-virtual, since jsdom can't exercise real virtualization layout.
- Add a test:ci script to @librechat/client and a Tests: @librechat/client CI job so packages/client specs run on every frontend PR.
The gitnexus droplet is ~8.7GB usable, not the 60GB the disk-cleanup
comment assumed. With /usr (~2.8GB), the in-use docker images (~2.1GB),
and the growing /opt/gitnexus/indexes (~1.2GB), deploys were aborting at
the `AVAIL_MB < 2048` guard ("Disk critically low").
Two fixes:
- Reclaim the previous gitnexus image after force-recreate. The pre-pull
`docker system prune -af` cannot remove it while the old container is
still running, so a stale ~700MB generation accumulated every deploy.
A post-recreate `docker image prune -f` makes the box self-cleaning.
- Lower the abort threshold 2048 -> 1536MB. The image is ~700MB and
shares most layers with the running one, so an incremental pull needs
well under 1GB; the old guard was sized for the 60GB assumption.
Also corrects the stale 60GB comment to reflect the actual disk.
* 🗜️ ci: Cache Dependencies and Builds in Cache Integration Tests
Port the node_modules and package-dist caching pattern from
backend-review.yml to cache-integration-tests.yml, which ran a full
npm ci (~72s) and rebuilt data-provider, data-schemas, and api on
every run. Cache keys are identical to backend-review.yml so the two
workflows share entries. Drops setup-node's npm tarball cache,
superseded by the node_modules restore, matching backend-review.yml.
* 🗜️ ci: Exercise Warm-Cache Path
* 🪟 ci: Shard Windows Frontend Unit Tests
Mirror the 4-way jest sharding the Ubuntu frontend test job already
uses onto the Windows job, which currently runs the whole client suite
in a single 20-minute job. Also drops the `--verbose` flag, which npm
consumed itself (it preceded `--`) and only raised npm's own log level.
* 🪟 ci: Trigger Frontend Tests on Workflow Changes
* 👷 ci: Add API runtime smoke (boot the production image) to docker-smoke
The docker-smoke workflow only built the `client-package-build` stage and
never booted the runtime, so it couldn't catch the class of regression that
recently took production down: the api tsdown bundle externalizes runtime
deps that, after `npm ci --omit=dev`, were missing from the image
(`Cannot find module 'get-stream'`).
- Add an `api-runtime-smoke` job that builds the real production image
(final `api-build` stage, `npm ci --omit=dev`), then:
1. loads the @librechat/api bundle's full require graph in the pruned
image (deterministic, no DB) — fails on any missing/ESM-incompatible
runtime dependency.
2. boots the actual entrypoint and asserts no module-load crash (the
server loads its require graph before connecting to Mongo, so this
surfaces without a database).
- Expand triggers to include `packages/api/**`, `packages/data-schemas/**`,
and `api/package.json` (previously a packages/api change only triggered
this via a root lockfile change, and even then only built the client stage).
- Add gha build cache + concurrency cancellation to bound CI cost.
* 👷 ci: Address Codex review — boot smoke against real Mongo + crash detection
- Boot the production image against a real MongoDB container with the env
the server needs, so the *entire* require graph loads. `api/db/connect.js`
throws at module scope without `MONGO_URI` and is imported before
models/services/routes, so the previous no-env boot exercised almost none
of the legacy API graph. (Codex finding 2)
- Gate on `/health` returning 200 AND the container staying alive, failing on
any container exit. A non-module startup crash (ReferenceError, SyntaxError,
bad config) now fails the smoke instead of slipping past a missing-module
grep. (Codex finding 3)
- Expand trigger from `api/package.json` to `api/**`, since the image copies
the whole `api/` tree and runs `node server/index.js`. (Codex finding 1)
* 👷 ci: Address Codex round 2 — poll /readyz + cover all image inputs
- Poll /readyz instead of /health. /health returns 200 at app.listen, but
initializeMCPs() and checkMigrations() run *after* listen and process.exit(1)
on failure; /readyz only returns 200 once serverReady is set after those
complete. So post-listen startup crashes now fail the smoke too. (finding A)
- Expand triggers to every source tree copied into the production image:
client/**, config/**, skill/** (the final stage copies client/dist, config,
and skill). (finding B)
* ⚡ refactor: Migrate @librechat/client build from Rollup to tsdown
Mirrors the data-schemas migration. Replaces Rollup (rpt2 + postcss) with
tsdown (rolldown + oxc); the package build drops from tens of seconds to ~0.3s.
- Emit isolated-declaration .d.ts via oxc (dts.oxc) and enforce
isolatedDeclarations in tsconfig for editor DX (source made clean: explicit
export type annotations added across src, no `any`).
- Extract component CSS to dist/style.css so the CJS output stays valid
CommonJS (the prior postcss runtime-injection produced an ESM import in the
CJS bundle that breaks jest/require). Imported once in the client app entry;
Vite bundles it for the app.
- Repoint package.json to dual .mjs/.cjs + .d.mts/.d.cts and add ./style.css
and ./package.json exports.
- Update CI build-cache keys to hash tsdown.config.mjs; remove rollup.config.js.
* 🔧 chore: address Codex review on client tsdown migration
- Add tsdown.config.mjs to turbo.json build `inputs` so changes to the new
bundler config invalidate the Turbo cache (the shared inputs only listed the
rollup configs). Also covers the already-migrated data-schemas.
- Name the memoized default export (ControlComboboxMemo) instead of the
codefix-generated `_default_1`, for clearer stack traces / grepping.
Replace the Rollup + `rollup-plugin-typescript2` build with a split
pipeline: tsdown (rolldown) bundles the JS in ~0.2s, and plain `tsc`
emits the declarations to `dist/types` (~2s). Full cold build drops from
~9.2s to ~2.5s (~3.6x) with zero source changes.
Unlike data-schemas, the fast oxc/isolated-declarations dts path isn't
viable here: the package's 78 exported zod schemas produce 374
`isolatedDeclarations` errors (TS9013/TS9038) and a `z.ZodType<T>`
annotation would break the 76 downstream `.extend`/`.shape`/`.pick`
usages. Plain `tsc` keeps the rich zod types intact, and since dts was
never the bottleneck (rollup-plugin-typescript2 was), the win stands.
- dts stays unbundled in `dist/types/` — identical to the prior output,
so the existing deep `dist/types` imports and the exports `types`
paths are unchanged.
- ESM output renamed `index.es.js` -> `index.mjs` (via the exports map;
no consumer hardcodes the old path). cjs/types paths unchanged.
- `./react-query` now emits a real cjs build + types — the exports map
already promised them, but Rollup only ever built the esm file.
- Kept `rollup` + the plugins used by `server-rollup.config.js`
(the `rollup:api` server-bundle smoke test in backend-review.yml);
removed only the deps used solely by the deleted `rollup.config.js`.
- Repointed CI build-cache keys from `rollup.config.js` to
`tsdown.config.mjs`.
* ⚡️ refactor: Migrate @librechat/api build to tsdown
Replace Rollup with tsdown (rolldown + oxc isolated-declarations) for the
@librechat/api package build, mirroring the merged data-schemas migration.
- Add tsdown.config.mjs (cjs output, oxc dts, externalize all bare deps,
bundle first-party `~/` + relative imports)
- Annotate exports for isolatedDeclarations (codefix-driven). Collapse the
tokens.ts model->token maps to Record<string, Record<string, number>> and
switch validation.ts's runtime `files` field from z.any() to z.unknown()
so no explicit `any` is introduced
- Repoint package.json main/types/exports to tsdown's .cjs/.d.cts output
- Add src/telemetry.ts entry shim so the two index.ts entries don't collide
in oxc's flat dts output (stable dist/telemetry.{cjs,d.cts})
- Delete rollup.config.js
Build time ~36s -> ~0.5s. No runtime behavior change: 5712 unit tests pass,
both entries load via require(), legacy /api consumes them unchanged.
* 👷 ci: Hash packages/api/tsdown.config.mjs in build-api cache keys
The build-api cache keys hashed `packages/api/server-rollup.config.js`,
which never existed (api used `rollup.config.js`, now removed) — a copy-paste
artifact from the data-provider key that matched no file. Replace it with the
new `packages/api/tsdown.config.mjs` so edits to the build config (entry,
format, externals) bust the api build cache, matching the data-schemas key.
* ⚡ perf: Migrate data-schemas Build to tsdown with isolatedDeclarations
Replace Rollup with tsdown (rolldown + oxc) for @librechat/data-schemas. With the source made isolatedDeclarations-clean, oxc emits .d.ts without tsc, dropping the package build from ~5.8s to ~0.8s (~7x).
- Annotate exported model/method factories for isolatedDeclarations (TypeScript's fixMissingTypeAnnotationOnExports codefix plus hand-authored interfaces); type the ~44 mongoose `any`s and add an explicit PromptMethods interface (previously its declaration was silently dropped by the Rollup build).
- Repoint package.json exports/main/module/types to tsdown output; drop rollup config.
- Config lives in tsdown.config.mjs (native ESM) so CI without a TS-config loader can build it; bundle `dotenv` so the package stays self-contained for its env-loading side effect.
- Fix a latent token `metadata` mismatch the accurate types surfaced: widen TokenCreate/UpdateData inputs to accept plain objects, flatten OAuthMetadata at the api boundary.
- Update mongoMeili/aclEntry specs to the precise model types; drop redundant terser minification from data-provider's library build.
All data-schemas tests pass; api builds clean against the new output.
* 🔧 chore: Hash tsdown.config.mjs in data-schemas CI build-cache keys
The data-schemas build switched from rollup to tsdown, but the build-data-schemas / build-api cache keys in backend-review, config-review, and playwright-mock still hashed the (now-deleted) rollup.config.js. Hash tsdown.config.mjs instead so a config-only change invalidates the cached dist/api builds. (Found by Codex review.)
* 🔧 chore: Replace deprecated tsdown `external` with `deps.neverBundle`
tsdown 0.22 deprecated the top-level `external` option in favor of `deps.neverBundle`. Migrate the data-schemas config and set `deps.onlyBundle: false` to silence the (intentional) dotenv bundling hint. Build output and externalization are unchanged — dotenv bundled, all peers external.
* 🩹 fix: Bump GitNexus to 1.6.5 and Fail-Soft the PR Index Job
The GitNexus Index workflow began failing on most PRs with
"Analysis failed: Maximum call stack size exceeded". Root cause is in
the pinned gitnexus@1.5.3 CLI: pipeline.js does
`deferredWorkerCalls.push(...chunkWorkerData.calls)`, and once a chunk
yields more extracted calls than V8's argument-count limit (~125k on
this repo) the spread-push throws a RangeError. It is deterministic on
repo size, not flaky — LibreChat simply grew past the threshold, so it
fails "more often" as more branches cross it. Stack-size flags don't
help; it's an arg-count limit, not stack depth.
gitnexus@1.6.5 refactored that code path (the .calls spread-pushes are
gone) and indexes this repo cleanly. Bump the indexer, the deploy image
tag/build-arg, and the Dockerfile default in lockstep (an index written
by 1.6.5 must be served by a 1.6.5 server), and move the co-pinned
@ladybugdb/core to 0.16.1 to match.
Also make the index job fail-soft on pull_request events so a future
tool-internal crash degrades gracefully instead of red-X'ing PRs. Push,
dispatch, and /gitnexus command runs still fail loudly, keeping the
deploy-gating and completion-comment logic correct.
* 🐳 fix: Unbreak the GitNexus Deploy Image for 1.6.5
Addresses two issues in the deploy image surfaced after the 1.6.5 bump:
- The image build's lbug-adapter patch grepped
dist/mcp/core/lbug-adapter.js for "LOAD EXTENSION fts", but in 1.6.5
that file is a shim re-export and the FTS load moved to
dist/core/lbug/lbug-adapter.js. The grep would fail the build on the
next image rebuild. The patch is also obsolete: 1.6.5 loads the vector
extension itself via loadVectorExtension. Removed the patch step.
- The image installed only gitnexus, letting @ladybugdb/core resolve
freely via gitnexus's ^0.16.1 range while the index workflow pins
0.16.1 exactly. Pin the native DB in the image too (nested under
gitnexus so install-extensions.js keeps resolving it), restoring the
intended indexer/server lockstep.
The `client/` workspace was never type-checked: the existing typecheck
job only covered `packages/` and `api/`, and Vite/esbuild transpiles
without type-checking, so type errors shipped through every CI gate.
- Add a `typecheck` job to frontend-review.yml running `tsc --noEmit`
over `client/` (zero tolerance), reusing the data-provider +
client-package build artifacts. Triggers on `client/**`,
`packages/client/**`, `packages/data-provider/**`.
- Fix all 168 pre-existing client type errors this surfaced (source +
tests), including genuine latent bugs:
- `getFileConfig()` was typed as merged `FileConfig`, but the server
returns the raw config that `mergeFileConfig()` consumes (`TFileConfig`).
- SidePanel/Agents `Retrieval`/`ImageVision` were bound to `AgentForm`
but use the assistants `Capabilities` enum → `AssistantForm`.
- `useSearchResultsByTurn` read a `sources` field its type lacked.
- Removed orphaned dead code: `Artifacts/Mermaid.tsx` (imported a
never-installed dep) and dead barrel re-exports (`./Plugins`, `./MCPAuth`).
- Narrow `client/tsconfig.json` to the client app (drop `../e2e` and
`../config/translations`, which reference backend/tooling modules) so
the gate's scope matches its trigger.
No `any`/`@ts-ignore`/`as unknown as`. Localized newly-surfaced strings.
* 🔧 chore: Update ESLint config, add import sorting script, Test Sharding, Bump `@librechat/agents`
* Change 'no-nested-ternary' rule from 'warn' to 'error' in ESLint config
* Add new scripts for sorting imports in the project
* Update lint-staged configuration to include import sorting
* Modify GitHub Actions workflows to support sharding for unit tests
* chore: remove nested ternary expressions
* refactor: Extract scale multiplier logic into a separate function in CircleRender component
* refactor: Simplify auto-refill rendering logic in Balance component for better readability
* refactor: Improve width style handling in DataTable components for clarity and maintainability
* chore: remove CircleRender component
* delete: Remove CircleRender component as it is no longer needed in the project
* chore: Bump @librechat/agents to version 3.2.31 and update Node.js engine requirement
* Update @librechat/agents dependency from 3.2.2 to 3.2.31 in package-lock.json, api/package.json, and packages/api/package.json
* Change Node.js engine requirement from >=20.0.0 to >=24.0.0 in @librechat/agents
* chore: Add import sorting check to ESLint CI workflow
* Implement a new job in the GitHub Actions workflow to verify import ordering on changed files.
* The job checks for changes in specific file types and reports any import order drift, providing instructions for local fixes.
* 🎭 test: Run Mock E2E Suite Through createRun With In-Process Fake Model
Replace the standalone HTTP mock LLM server with an in-process fake model
injected into the real createRun -> Run.create pipeline via
run.Graph.overrideTestModel, so the mock suite exercises the agents
integration end-to-end without a live provider or a separate server.
- Bump @librechat/agents to 3.2.2 for the FakeChatModel/createFakeStreamingLLM exports
- Add an env-gated applyTestRunHook seam in packages/api createRun (no /api changes)
- Add e2e/setup/fake-model.js to drive default replies + the skill-authoring tool-call flow
- Drop the mock-llm webServer from playwright.config.mock.ts and set LIBRECHAT_TEST_RUN_HOOK
* 🧹 test: Retire Standalone Mock LLM Server From E2E Recorder
Migrate the `--profile=mock` recorder onto the same in-process fake model
as the Playwright mock suite, then delete the now-unused HTTP mock server
so the fake-LLM logic lives in a single place.
- Point record.js mock profile at the fake model via LIBRECHAT_TEST_RUN_HOOK
- Remove the mock-llm-server spawn/wait and MOCK_LLM_PORT plumbing from record.js
- Delete e2e/setup/mock-llm-server.js (e2e/setup/fake-model.js is now the only source)
- Update e2e/README.md to describe the in-process fake LLM
* 🏷️ ci: Rename Playwright Mock E2E Check to Playwright E2E Tests
* feat: Add granular access control to shared links via ACL system
* fix(shared-links): preserve isPublic on failed migration grants
Transient ACL failures during auto-migration permanently stranded
links — $unset ran unconditionally, removing the legacy flag that
triggers retry. Now only $unset isPublic after all grants succeed.
* fix(config): skip isPublic unset for failed ACL grants
Bulk migration unconditionally removed isPublic from all links,
even those whose ACL writes failed. Failed links then lost the
legacy marker needed for auto-migration retry. Now tracks failed
link IDs per-batch and excludes them from the $unset step.
Also adds sharedLink to AccessRole resourceType schema enum —
was missing, only worked because seedDefaultRoles uses
findOneAndUpdate which bypasses validation.
* ci(config): add jest config and PR workflow for migration tests
config/__tests__/ specs depend on api/jest.config.js module
mappings but had no dedicated runner. Adds config/jest.config.js
extending api config with absolutized paths, npm test:config
script, and a GitHub Actions workflow triggered by changes to
config/, api/models/, api/db/, or packages/ ACL code.
* fix(permissions): honor boolean sharedLinks config
SHARED_LINKS has no USE permission, so boolean config produced
an empty update payload — gate conditions only matched object
form, making `sharedLinks: false` a no-op on existing perms.
* fix(share): resolve role before creating shared link
Role lookup between create and grant left an orphaned link
without ACL entries if getRoleByName threw — retry then hit "Share already exists" with no recovery path.
* fix: Restore Public ACL Access Checks
* fix: Type Public ACL Lookup
* fix: Preserve Private Legacy Shared Links
* chore: Promote Shared Link Permission Migration
* fix: Address Shared Link Review Findings
* fix: Repair Shared Link CI Follow-Up
* fix: Narrow Shared Link Mongoose Test Mock
* fix: Address Shared Link Review Follow-Ups
* fix: Close Shared Link Review Gaps
* fix: Guard Missing Shared Link Permission Backfill
* test: Add Shared Link Mock E2E
* test: Stabilize Shared Link Mock E2E
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
The `Tests: @librechat/api` job in `backend-review.yml` had
`timeout-minutes: 10`. Recent runs have started getting cancelled
right at the 10-minute mark with tests still actively passing — the
log streams `PASS …` lines through the final second before
`The operation was canceled.` fires.
Looking at recent runs in the wild:
- Warm `ubuntu-latest` runners: suite finishes in ~4–5 min.
- Cold/busy runners: 10+ min. No headroom = job killed mid-suite.
The suite has grown over the last few months (OpenTelemetry, MCP
OAuth, Bedrock, the Operational Prometheus Metrics work from #13265,
plus the agents/responses harness). 10-minute ceiling that worked
before is now a tail-latency cliff.
Bump to 20 minutes. Still well below the runner's hard 6-hour cap,
generous enough to absorb runner variance, and a stuck/genuine hang
will still get killed eventually rather than billing forever.
No code changes; one-line config bump.
Adds a `Run Prettier --check on changed files` step to the existing
`eslint-ci.yml` workflow. Same path filter (api/**, client/**,
packages/**), same changed-files detection, runs after the ESLint step.
## Why
Today there is no `prettier --check` in CI — only the local
`lint-staged` pre-commit hook runs `prettier --write`. When a PR is
merged with the hook bypassed (e.g. GitHub UI edit-and-merge, or
`git commit --no-verify`), a file can land in a non-prettier-canonical
state and nobody notices. The next contributor who stages an unrelated
change in that file then ends up with a "drive-by" prettier diff in
their PR.
`packages/api/src` had 14 such files accumulated; #13281 fixes the
existing drift. This PR closes the gap so it doesn't regrow.
## What the step does
- Detects changed JS/TS files under `api/**`, `client/**`, or
`packages/**` against the PR base.
- Runs `npx prettier --check $CHANGED_FILES`.
- On failure, prints a `::error::` annotation telling the contributor
how to fix it locally (`npx prettier --write <files>`).
Same one-step shape as the existing ESLint check — no extra workflow
file, no extra `npm ci`, no extra checkout.
## Ordering note
#13281 (`chore: prettier --write packages/api/src`) should land first
so the existing drift is cleared. After both PRs merge, the
pre-commit hook + this CI check together prevent drift from
re-accumulating.
## Test plan
- [x] `npx js-yaml .github/workflows/eslint-ci.yml` validates.
- [ ] CI green on this PR itself (touches only `.github/workflows/`,
which the path filter includes, so the workflow runs on itself).
- [ ] After merge: a synthetic PR introducing prettier drift should
fail the new step with the diagnostic message.