Skip to content

πŸ”Ž feat: Filter the Conversation List by Date, Endpoint, Files, and Sharing - #16245

Open
berry-13 wants to merge 16 commits into
canaryfrom
berry-13/convo-list-filters-api
Open

berry-13 wants to merge 16 commits into
canaryfrom
berry-13/convo-list-filters-api

Conversation

@berry-13

@berry-13 berry-13 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part 3 of 6 of the quieter-layout stack. #16244 has merged, so this is based on canary.

The conversation list query takes five new facets: a date range, endpoints, hasFiles and sharedOnly. Dates and endpoints narrow the query directly, hasFiles matches conversations with a file on the conversation or on any of its messages, and sharedOnly resolves the user's live share links, because a share expires and a denormalized flag on the conversation would outlive it.

parseConversationListFilters in packages/api owns what a valid facet is, so the route only calls it and returns its 400. A malformed date or a mistyped flag fails the request rather than being dropped, and the endpoint list is capped (50 names of 128 characters by default, conversationList in librechat.yaml) to keep an unbounded $in out of the query. Two indexes back the new paths, and cursor pagination is unchanged.

Type of change

  • Feature

Testing

Tested environments/configuration:

  • Linux, Node 24, mongodb-memory-server

Automated tests:

  • packages/api: npx jest src/conversations/filters (passing)
  • packages/data-schemas: npx jest src/methods/conversation.spec (passing)
  • api: npx jest server/routes/__tests__/convos.spec (103 passing, through the real parser)
  • Playwright mock harness: six @scenario tests in e2e/specs/mock/scenarios/conversation-list-filters.spec.ts (date cutoffs, endpoint list, message attachments, live and expired shares, malformed facets, unfiltered list), 18/18 passing across desktop light, desktop dark and mobile
  • npx tsc --noEmit in packages/api and packages/data-schemas

Screenshots / recordings

No user-facing change. The UI that uses these filters is the next PR in the stack.

Risk / compatibility

Adds two indexes: { user, isArchived, endpoint, updatedAt, _id } on conversations and { user, conversationId } on shares. Requests without the new query parameters behave exactly as before.

Checklist

  • I reviewed my own changes
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • Required documentation PR: N/A

@berry-13
berry-13 added this pull request to stack #16249 September 23, 2026 15:15
@berry-13
berry-13 marked this pull request as ready for review September 23, 2026 16:42
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
πŸ“ Code Review βœ… Completed 2026-09-23T16:54:05.511865Z 4f3749a Draft marked ready
πŸ”’ Security Review βœ… Completed 2026-09-23T17:01:10.067555Z 4f3749a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with πŸ‘€ while any review is running, comments if it has suggestions, and reacts with πŸ‘ once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f3749ae70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/data-provider/src/types/queries.ts
Comment on lines +3103 to +3106
/* `files` is absent on most rows and `[]` on rows that lost their last attachment,
so both have to read as "no attachments". */
if (hasFiles === true) {
filters.push({ files: { $exists: true, $not: { $size: 0 } } } as FilterQuery<IConversation>);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive the file facet from actual message attachments

Filtering only on Conversation.files excludes conversations whose attachments are stored on messages. In particular, the Assistants flow writes uploads to userMessage.files and attempts to write convo.file_ids (api/server/services/Threads/manage.js:94-108), while the conversation schema defines only files (packages/data-schemas/src/schema/convo.ts:368-370); therefore Assistants conversations containing uploaded files never satisfy this predicate. The facet needs to query or maintain a field derived from all persisted message attachments rather than relying solely on this conversation field.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50eb85f: hasFiles now OR-matches conversations with a file on any user message alongside Conversation.files. Verified on 05569a9 by list-files-facet-reads-message-attachments (message-only upload, imported files, emptied files[], plain chat).

Comment on lines +42 to +46
const date = new Date(raw);
if (Number.isNaN(date.getTime())) {
return { invalid: true };
}
return { date };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate ISO dates before constructing them

new Date(raw) is substantially more permissive than the documented ISO 8601 contract and normalizes invalid calendar dates. For example, updatedAfter=2026-02-30 is accepted as March 2, while updatedAfter=1 becomes January 1, 2001, so malformed requests silently apply an unintended cutoff instead of returning the promised 400 response. Validate the syntax and calendar value before accepting the parsed date.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50eb85f: parseDate checks ISO 8601 syntax and calendar read-back before accepting, so 2026-02-30 and 1 answer 400. Verified on 05569a9 by list-malformed-facet-refused.

Comment on lines +118 to +122
if (firstValue(query.hasFiles) === 'true') {
filters.hasFiles = true;
}
if (firstValue(query.sharedOnly) === 'true') {
filters.sharedOnly = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject malformed boolean facet values

Only the exact string "true" is recognized, while every other supplied valueβ€”including typos such as hasFiles=tru, objects, or unsupported casingβ€”is silently treated as an absent filter. In those cases the endpoint returns a broader conversation list even though the parser's contract says malformed facets fail the request. Explicitly accept true and false, and return 400 for any other supplied value for both flags.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50eb85f: hasFiles/sharedOnly accept only true or false and answer 400 otherwise. bf38f58 runs the route spec through the real parser; verified on 05569a9 by list-malformed-facet-refused.

Comment on lines +27 to +30
const MAX_ENDPOINT_FILTERS = 50;

/** One endpoint name. Long enough for a custom endpoint, short enough to bound the query. */
const MAX_ENDPOINT_LENGTH = 128;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make the endpoint-filter limits configurable

The new count and name-length limits are fixed at 50 and 128, so deployments exposing more endpoints or longer custom endpoint identifiers reject otherwise legitimate filter requests with no librechat.yaml setting to adjust the policy. Add corresponding configSchema fields with these values as defaults and inject the resolved limits into the parser.

AGENTS.md reference: AGENTS.md:L66-L68

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50eb85f: conversationList.maxEndpointFilters and maxEndpointNameLength in configSchema, defaults 50/128, injected by the route. bf38f58 asserts a configured limit of 2 rejects three endpoints.

Comment thread packages/data-schemas/src/methods/conversation.ts Outdated
Comment on lines +3109 to +3110
if (sharedOnly === true) {
const sharedIds = await getSharedConversationIds(user);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse the shared-ID lookup when marking rows

With sharedOnly=true, getSharedConversationIds has already established that every conversation admitted by this filter has an active share, but the function later calls attachSharedFlags unconditionally at packages/data-schemas/src/methods/conversation.ts:3306, issuing another SharedLink query after the conversation query. This adds a redundant serial database round trip to every filtered page and can even leave a qualifying row without isShared if that second best-effort lookup fails. Retain the ID set and mark the returned rows directly, or skip the second lookup for this path.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 50eb85f: the resolved share set marks isShared directly and attachSharedFlags only runs on unfiltered pages. Verified on 05569a9 by list-shared-facet-follows-live-links, which asserts isShared on every returned row.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05569a917a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/routes/convos.js Outdated
!projectId || projectId === 'unassigned' || /^[a-f\d]{24}$/i.test(projectId);

router.get('/', async (req, res) => {
router.get('/', configMiddleware, async (req, res) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep config resolution off the conversation-list hot path

On a cache-cold sidebar request, adding configMiddleware makes the list wait for effective-config resolution before starting getConvosByCursor; getAppConfig first resolves user principals and then applicable config overrides, which can add two serial database reads to the visible conversation's load even when the request uses no new facets. Resolve these limits without the per-user config middleware, or reuse configuration/principals already loaded for the request so ordinary list requests retain their previous latency.

AGENTS.md reference: AGENTS.md:L40-L42

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a01e7e9: the list route no longer runs configMiddleware; the limits are deployment-level and come from the in-memory base config (getAppConfig({ baseOnly: true })). It also exposed that conversationList never reached AppConfig at all, so AppService now carries it (service.spec covers both).

Comment thread packages/data-schemas/src/methods/conversation.ts Outdated
Comment on lines +45 to +46
const ISO_DATE =
/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(Z|[+-](?:0\d|1[0-4])(?::?[0-5]\d)?)?)?$/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a timezone on timestamp cutoffs

When a caller supplies an accepted timestamp without an offset, such as updatedAfter=2026-09-01T12:00:00, new Date() interprets it in the server process's local timezone even though these parameters are documented as absolute cutoffs. The identical request therefore becomes 16:00Z on a New York host but 03:00Z on a Tokyo host and can return different conversations after a deployment or timezone change; require Z or an explicit offset whenever a time component is present, or deliberately interpret zone-less timestamps as UTC.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 272ce3c: a timestamp without Z or an offset is read as UTC, the same as a date-only cutoff, so the host zone no longer changes the result. Covered in filters.spec.

@berry-13
berry-13 force-pushed the berry-13/convo-list-filters-api branch from 05569a9 to 9f15215 Compare September 25, 2026 13:12
@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

β”‚ 24      β”‚ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             β”‚ 2985.68200000003   β”‚ 4257.045000000042  β”‚ 200    β”‚
β”‚ 25      β”‚ 'http://localhost:3080/api/keys?name=openAI'                                                                    β”‚ 3229.4529999999795 β”‚ 3908.874000000069  β”‚ 200    β”‚
β”‚ 26      β”‚ 'http://localhost:3080/api/presets'                                                                             β”‚ 3230.4990000000107 β”‚ 3916.773999999976  β”‚ 200    β”‚
β”‚ 27      β”‚ 'http://localhost:3080/api/tags'                                                                                β”‚ 3230.7060000000056 β”‚ 3917.3000000000466 β”‚ 200    β”‚
β”‚ 28      β”‚ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     β”‚ 3231.1199999999953 β”‚ 4257.424000000057  β”‚ 200    β”‚
β”‚ 29      β”‚ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       β”‚ 3232.7870000000694 β”‚ 4415.691999999981  β”‚ 200    β”‚
β”‚ 30      β”‚ 'http://localhost:3080/api/files/config'                                                                        β”‚ 3234.1170000000275 β”‚ 4172.3520000000135 β”‚ 200    β”‚
β”‚ 31      β”‚ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        β”‚ 3234.3130000000237 β”‚ 6943.164000000048  β”‚ 200    β”‚
β”‚ 32      β”‚ 'http://localhost:3080/api/endpoints/token-config'                                                              β”‚ 3235.0870000000577 β”‚ 4428.695000000007  β”‚ 200    β”‚
β”‚ 33      β”‚ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              β”‚ 3235.51800000004   β”‚ 4766.119000000006  β”‚ 200    β”‚
β”‚ 34      β”‚ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' β”‚ 4515.231000000029  β”‚ 4769.994999999995  β”‚ 200    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ audit                      β”‚ median               β”‚ limit β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'largest-contentful-paint' β”‚ 4537.641             β”‚ 4500  β”‚
β”‚ 1       β”‚ 'cumulative-layout-shift'  β”‚ 0.018445707632869273 β”‚ 0.1   β”‚
β”‚ 2       β”‚ 'total-blocking-time'      β”‚ 249.61000000000013   β”‚ 500   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

  1) [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4537.641

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


πŸ€–: global teardown has been started
2026-09-25 13:16:18 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-25 13:16:18 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  βœ…  Found user in Database
πŸ€–:  βœ…  Deleted 1 convos & 2 messages
πŸ€–:  βœ…  Deleted user from Database
2026-09-25 13:16:19 οΏ½[31merrorοΏ½[39m: οΏ½[31mIndex build failed for "Message": Operation interrupted because client was closedοΏ½[39m
2026-09-25 13:16:19 οΏ½[31merrorοΏ½[39m: οΏ½[31mIndex build failed for "User": Operation interrupted because client was closedοΏ½[39m
πŸ€–: global teardown has been started
2026-09-25 13:16:19 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-25 13:16:19 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
2026-09-25 13:16:19 οΏ½[31merrorοΏ½[39m: οΏ½[31mIndex build failed for "Conversation": Operation interrupted because client was closedοΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  ⚠️  User not found in Database
  1 failed
    [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

Open the full run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f1521533c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/routes/convos.js Outdated
tags = Array.isArray(req.query.tags) ? req.query.tags : [req.query.tags];
}

const { conversationList } = await getAppConfig({ baseOnly: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the config lookup out of the CJS route

This adds a service call and configuration behavior directly to /api, although the repository boundary requires CJS routes to retain only wiring and delegate behavior to packages/api. Keeping getAppConfig here also couples the route to the application singleton and makes the filter path harder to test or reuse; move the list handler/config resolution into the TypeScript module and inject the config reader and database method from this route.

AGENTS.md reference: AGENTS.md:L53-L57

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the 'Resolve the list filter limits in one place' commit on 6302c93: the base-config read lives in resolveConversationListFilters in packages/api, which receives getAppConfig from the route; the route keeps only that call. 6302c93 also keeps it inside the route's try.

Comment thread packages/data-provider/src/config.ts Outdated
Comment on lines +3208 to +3211
maxEndpointFilters: z.number().int().min(1).max(1_000).optional(),
/** One endpoint name. Long enough for a custom endpoint, short enough to
* bound the query. Omission keeps 128. */
maxEndpointNameLength: z.number().int().min(1).max(1_024).optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Define the limit defaults in configSchema

When either setting is omitted, the validated config leaves it undefined and packages/api/src/conversations/filters.ts supplies a separate hard-coded default, so the schema is not the required source of truth and future changes can make validation, documentation, and runtime behavior drift. The fresh evidence in this head is that both final schema fields remain .optional() while 50 and 128 are still defined separately in DEFAULT_CONVERSATION_LIST_LIMITS; apply those defaults in configSchema and consume the resolved values instead.

AGENTS.md reference: AGENTS.md:L66-L68

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on 6302c93: 50 and 128 are now field defaults on conversationListConfigSchema, AppService resolves them through loadConversationListConfig (an invalid block keeps the defaults), and the parser takes the resolved values with no constants of its own. service.spec covers unset, partial and invalid blocks.

Comment on lines +3131 to +3132
if (sharedOnly === true) {
const activeShares = await getSharedConversationIds(user);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid serial facet lookups when both filters are active

When hasFiles=true and sharedOnly=true, the full message-attachment lookup is awaited before this independent shared-link lookup begins. This adds the two database latencies on every combined-filter page, and when the user has no active shares it scans and materializes the attachment history only to discard it at the early return below. Start the user-scoped lookups together, or resolve the cheaper short-circuit before performing the attachment query, rather than introducing serial reads on the conversation-list path.

AGENTS.md reference: AGENTS.md:L40-L42

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on 6302c93: the attachment and share lookups start together with Promise.all, so a combined page pays one round trip of latency instead of two.

@berry-13
berry-13 force-pushed the berry-13/convo-list-filters-api branch from 9f15215 to 6302c93 Compare September 25, 2026 13:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5ae89b064

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/data-schemas/src/methods/conversation.ts
Comment on lines +39 to +40
const ISO_DATE =
/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(Z|[+-](?:0\d|1[0-4])(?::?[0-5]\d)?)?)?$/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept valid ISO timestamps with longer fractions

The public parameter is documented as ISO 8601, but this expression accepts only one to three fractional-second digits. Common serializers emit six or nine digitsβ€”for example, Python can send 2026-09-01T00:00:00.123456Zβ€”so otherwise valid client requests receive a 400 response. Accept an arbitrary nonempty fractional part and truncate it to the millisecond precision supported by Date rather than rejecting the timestamp.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bd77594 (commit 'Accept list cutoffs with microsecond or finer fractions'): any fractional part is accepted and Date keeps the milliseconds; filters.spec covers 6 and 9 digits, with and without Z.

@berry-13
berry-13 force-pushed the berry-13/convo-list-filters-api branch from a5ae89b to bd77594 Compare September 25, 2026 14:59

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd77594597

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/conversations/filters.ts
Comment thread packages/api/src/conversations/filters.ts
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd77594597

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!Message) {
return null;
}
return Message.find({ user, 'files.0': { $exists: true } }).distinct('conversationId');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include nested attachments in the file facet

When a user attaches a file through a mid-run steer, applySteerPart persists the reference under message.content[].files (api/server/controllers/agents/client.js:814-827) rather than top-level message.files; a conversation with only such an attachment therefore never enters this ID set and is omitted by hasFiles=true. Fresh evidence beyond the earlier top-level-message fix is that the replay path explicitly reads these durable nested references at api/app/clients/BaseClient.js:1957-1969; include content-part file references in the attachment predicate as well.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 05eac27. The facet now matches files on message content parts (content.files.0) as well as message.files. Covered by a steer-part case in conversation.spec and by the list-files-facet-reads-message-attachments scenario, which seeds a chat whose only file rides on a steer; it passes on 05eac27 in desktop light, desktop dark and mobile.

@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05eac27f6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
return Message.find({
user,
$or: [{ 'files.0': { $exists: true } }, { 'content.files.0': { $exists: true } }],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match all persisted content-part file references

When a stored message's only attachment uses a provider-native content[].file, content[].image_file, or content[].file_id reference, this predicate omits its conversation because it checks only content.files.0. Fresh evidence beyond the earlier content[].files thread is that StoredMessagePartInput supports the singular file and image_file shapes, while modelBoundContent.ts explicitly discovers and replays all three forms; consequently hasFiles=true returns an incomplete list for these persisted messages. Extend the predicate to every supported content-part file-reference shape.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 218d269. The facet now matches every content-part shape replay reads (modelBoundContent.ts:390-401): files[], file.file_id, image_file.file_id and a bare file_id. Three conversation.spec cases cover the singular shapes and fail without the change; the list-files-facet-reads-message-attachments scenario seeds an image_file-only chat and passes on 218d269 in desktop light, desktop dark and mobile.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 218d269aee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +3003 to +3005
$or: [
{ 'files.0': { $exists: true } },
{ 'content.files.0': { $exists: true } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include persisted response attachments in the file facet

When a conversation's only file is an assistant or tool output, it is persisted under message.attachments rather than any of the paths in this predicate: BaseClient.js assigns completed artifactPromises to responseMessage.attachments, and the client renders those entries through ContentRender. With no user upload, Conversation.files also remains unset, so hasFiles=true omits conversations that visibly contain generated files; include attachment entries carrying a file_id in this query.

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6f6d2d6. The facet now reads attachments.file_id, so a chat whose only file is tool or assistant output qualifies; an attachment without a file_id (web search) does not. With message.files, attachments and every content-part shape, the predicate covers each Message field that holds a file. Covered by a conversation.spec case and by the list-files-facet-reads-message-attachments scenario, which seeds a tool-output-only chat and passes on 6f6d2d6.

@berry-13
berry-13 force-pushed the berry-13/convo-list-filters-api branch from 218d269 to 6f6d2d6 Compare September 25, 2026 20:32
@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

β”‚ 21      β”‚ 'http://localhost:3080/api/convos?pinned=true&limit=100'                                                        β”‚ 2636.4400000000314 β”‚ 3411.2550000000047 β”‚ 200    β”‚
β”‚ 22      β”‚ 'http://localhost:3080/api/mcp/servers'                                                                         β”‚ 2983.967000000004  β”‚ 4249.542000000016  β”‚ 200    β”‚
β”‚ 23      β”‚ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 β”‚ 2985.2810000000172 β”‚ 3744.607000000018  β”‚ 200    β”‚
β”‚ 24      β”‚ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             β”‚ 2985.850000000006  β”‚ 4248.130999999994  β”‚ 200    β”‚
β”‚ 25      β”‚ 'http://localhost:3080/api/keys?name=openAI'                                                                    β”‚ 3228.4770000000135 β”‚ 3912.94200000001   β”‚ 200    β”‚
β”‚ 26      β”‚ 'http://localhost:3080/api/presets'                                                                             β”‚ 3228.7540000000154 β”‚ 3919.5109999999986 β”‚ 200    β”‚
β”‚ 27      β”‚ 'http://localhost:3080/api/tags'                                                                                β”‚ 3229.8420000000333 β”‚ 3917.31700000001   β”‚ 200    β”‚
β”‚ 28      β”‚ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     β”‚ 3230.3640000000014 β”‚ 4249.77800000002   β”‚ 200    β”‚
β”‚ 29      β”‚ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       β”‚ 3231.0230000000156 β”‚ 4420.112000000023  β”‚ 200    β”‚
β”‚ 30      β”‚ 'http://localhost:3080/api/files/config'                                                                        β”‚ 3231.600000000035  β”‚ 4171.385000000009  β”‚ 200    β”‚
β”‚ 31      β”‚ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        β”‚ 3232.2850000000326 β”‚ 6936.326000000001  β”‚ 200    β”‚
β”‚ 32      β”‚ 'http://localhost:3080/api/endpoints/token-config'                                                              β”‚ 3232.7790000000386 β”‚ 4428.798999999999  β”‚ 200    β”‚
β”‚ 33      β”‚ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              β”‚ 3233.357000000018  β”‚ 4757.195000000007  β”‚ 200    β”‚
β”‚ 34      β”‚ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' β”‚ 4521.739000000001  β”‚ 4779.16300000003   β”‚ 200    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ audit                      β”‚ median               β”‚ limit β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'largest-contentful-paint' β”‚ 4519.224             β”‚ 4500  β”‚
β”‚ 1       β”‚ 'cumulative-layout-shift'  β”‚ 0.018448064092461254 β”‚ 0.1   β”‚
β”‚ 2       β”‚ 'total-blocking-time'      β”‚ 237.9050000000002    β”‚ 500   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

  1) [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4519.224

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


πŸ€–: global teardown has been started
2026-09-25 20:36:23 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-25 20:36:23 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  βœ…  Found user in Database
πŸ€–:  βœ…  Deleted 1 convos & 2 messages
πŸ€–:  βœ…  Deleted user from Database
πŸ€–: global teardown has been started
2026-09-25 20:36:24 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-25 20:36:24 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  ⚠️  User not found in Database
  1 failed
    [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

Open the full run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f6d2d6d41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

user,
$or: [
{ 'files.0': { $exists: true } },
{ 'attachments.file_id': { $type: 'string' } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include filepath-only attachments in the file facet

When a persisted tool/code result uses the supported download-only attachment shape (filename, filepath, and expiresAt, but no file_id), this condition is false, so a conversation whose only file has that shape is omitted by hasFiles=true. Fresh evidence beyond the prior attachments.file_id thread is the preserved historical shape in api/app/clients/specs/BaseClient.test.js:3299-3331 and the client rendering any attachment with a filepath in client/src/components/Chat/Messages/Content/Parts/Attachment.tsx:182-184; match valid filepath-only attachment records as well.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with πŸ‘Β / πŸ‘Ž.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1313b8e. An attachment now qualifies on a file_id or a non-empty filepath, the same rule Attachment.tsx:182-184 uses to render one as a file, so a download-only code output counts and a web search result does not. Covered by the conversation.spec attachments case and by the list-files-facet-reads-message-attachments scenario, which seeds a download-only chat and passes on 1313b8e.

…sharing

The list query takes five new facets. Dates and endpoints narrow it
directly; hasFiles matches conversations that carry at least one file;
sharedOnly resolves the user's live share links and matches their
conversation ids, since a share expires and a denormalized flag on the
conversation would outlive it.

parseConversationListFilters owns what a valid facet is, so the route keeps
the call and nothing else. A malformed date fails the request rather than
being dropped: a filter that is quietly ignored answers with the
conversations the user asked not to see. The endpoint list is bounded at 50
names of 128 characters to keep an unbounded $in out of the query.

Two indexes back the new paths: user, archived, endpoint, updatedAt on
conversations, and user, conversationId on shares.

Cursor pagination is unchanged, so a filtered list pages the same way an
unfiltered one does.
…parsing

The hasFiles facet matched only the conversation's own files array, which
the standard send flow never writes: uploads ride on user messages, so an
ordinary chat with an attachment was invisible to the filter. The facet
now OR-matches message-derived conversation IDs alongside the
conversation-level array.

Dates are validated as ISO 8601 before construction, so 2026-02-30 and a
bare year no longer roll over into an unintended cutoff, and a mistyped
hasFiles or sharedOnly value fails with 400 instead of reading as absent.
The endpoint count and name-length limits moved to conversationList in
configSchema and are injected into the parser, so a deployment serving
more endpoints can raise them. The shared filter resolves its IDs with
distinct and reuses the set to mark isShared, dropping the second
SharedLink query on every filtered page.
The route mock re-implemented the parser loosely: it ignored the configured
endpoint limits and dropped a mistyped flag, so a route that stopped
forwarding conversationList or stopped answering 400 still passed. The mock
now wraps the real parser, and the facet tests cover a mistyped flag and an
endpoint list over the configured limit.
Each facet is exercised against the running API with its own endpoint name, so the
rows a test seeds are the only rows the filter can return: date cutoffs on updated
and created time, an OR-matched endpoint list, an attachment that only exists on a
message, shares that are live, expiring or lapsed, malformed facets answering 400,
and a list without facets returning what it did before.
conversationList never reached the resolved app config: AppService builds it from
an explicit field list and the new knob was not on it, so a configured limit was
silently ignored and the defaults always applied. It is now carried onto AppConfig.

The list route also stops resolving the caller's merged config for it. The limits
are deployment-level, and configMiddleware put principal and override reads in
front of every cache-cold sidebar request, including ones with no facets; the base
config is in memory.
A timestamp without Z or an offset was handed to new Date(), which reads it in the
server's local zone, so the same request selected different conversations on hosts
in different zones. It is now UTC, matching how a date-only cutoff is read.
With hasFiles and sharedOnly both on, the message-attachment lookup finished
before the share lookup began, adding the two latencies on every page. Both are
independent user-scoped reads, so they now run in parallel.
The 50 and 128 defaults lived in both the parser and the schema's comments. They
are now field defaults on conversationListConfigSchema, AppService resolves them
(an invalid block keeps the defaults), and the parser takes the resolved values.

The base-config read moves out of the CJS route into
resolveConversationListFilters, which receives getAppConfig from the route.
The base config sits behind the config cache, which can be Redis, so every
sidebar request paid that round trip and could fail on it even though the limits
bound only the endpoint facet. A request without endpoints now reads no config.
The cutoff pattern allowed at most three fractional digits, so a valid ISO 8601
timestamp from a serializer that emits microseconds (Python, most databases)
answered 400. Any fraction is accepted now and Date keeps the milliseconds.
…he file facet

A file attached through a mid-run steer is stored on the steer content part
rather than on the message, so hasFiles=true left that conversation out.
Replay reads a file on a content part as files[], file.file_id,
image_file.file_id or a bare file_id; the facet matched only files[].
…acet

A file a tool produces is persisted on message.attachments, the one place
on a message the facet did not read, so a chat whose only file was
generated output was left out of hasFiles=true.
A code or tool result can persist an attachment with a filepath and no
file_id; the client renders any attachment with a filepath as a file,
so the facet now uses the same rule.
@berry-13
berry-13 force-pushed the berry-13/convo-list-filters-api branch from 1313b8e to c98f65d Compare September 25, 2026 21:44
@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

β”‚ 21      β”‚ 'http://localhost:3080/api/convos?pinned=true&limit=100'                                                        β”‚ 2628.9739999999874 β”‚ 3384.8769999999786 β”‚ 200    β”‚
β”‚ 22      β”‚ 'http://localhost:3080/api/mcp/servers'                                                                         β”‚ 2966.1300000000047 β”‚ 4236.156999999977  β”‚ 200    β”‚
β”‚ 23      β”‚ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 β”‚ 2967.2279999999737 β”‚ 3728.8979999999865 β”‚ 200    β”‚
β”‚ 24      β”‚ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             β”‚ 2967.8239999999932 β”‚ 4232.364999999991  β”‚ 200    β”‚
β”‚ 25      β”‚ 'http://localhost:3080/api/keys?name=openAI'                                                                    β”‚ 3215.5449999999837 β”‚ 3891.682999999961  β”‚ 200    β”‚
β”‚ 26      β”‚ 'http://localhost:3080/api/presets'                                                                             β”‚ 3216.5409999999683 β”‚ 3892.1659999999683 β”‚ 200    β”‚
β”‚ 27      β”‚ 'http://localhost:3080/api/tags'                                                                                β”‚ 3217.1659999999974 β”‚ 3896.0229999999865 β”‚ 200    β”‚
β”‚ 28      β”‚ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     β”‚ 3217.399999999994  β”‚ 4235.992999999988  β”‚ 200    β”‚
β”‚ 29      β”‚ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       β”‚ 3218.465999999986  β”‚ 4402.207999999984  β”‚ 200    β”‚
β”‚ 30      β”‚ 'http://localhost:3080/api/files/config'                                                                        β”‚ 3218.6669999999867 β”‚ 4151.203999999969  β”‚ 200    β”‚
β”‚ 31      β”‚ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        β”‚ 3218.8709999999846 β”‚ 6922.68299999999   β”‚ 200    β”‚
β”‚ 32      β”‚ 'http://localhost:3080/api/endpoints/token-config'                                                              β”‚ 3220.597000000009  β”‚ 4410.426999999996  β”‚ 200    β”‚
β”‚ 33      β”‚ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              β”‚ 3220.780999999988  β”‚ 4742.77899999998   β”‚ 200    β”‚
β”‚ 34      β”‚ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' β”‚ 4521.869999999966  β”‚ 4776.362999999983  β”‚ 200    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ audit                      β”‚ median               β”‚ limit β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'largest-contentful-paint' β”‚ 4531.997             β”‚ 4500  β”‚
β”‚ 1       β”‚ 'cumulative-layout-shift'  β”‚ 0.018450445013571484 β”‚ 0.1   β”‚
β”‚ 2       β”‚ 'total-blocking-time'      β”‚ 258.57699999999977   β”‚ 500   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

  1) [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4531.997

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


πŸ€–: global teardown has been started
2026-09-25 21:48:32 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-25 21:48:32 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  βœ…  Found user in Database
πŸ€–:  βœ…  Deleted 1 convos & 2 messages
πŸ€–:  βœ…  Deleted user from Database
πŸ€–: global teardown has been started
2026-09-25 21:48:32 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-25 21:48:32 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  ⚠️  User not found in Database
  1 failed
    [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

Open the full run

@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting β€œ@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants