LibreChat/packages/api/src/files
Danny Avila 562bd8ec5f
🐛 fix: Prevent Infinite Render Loop on Code-Execution File Preview (#13922)
* 🐛 fix: Prevent Infinite Render Loop on Code-Execution File Preview

Loading a conversation that contains a large (>1MB) code-execution
office file crashed the whole app with React error #185 ("Maximum
update depth exceeded") on hard refresh.

Root cause (client-only): the terminal-write effect in
useAttachmentPreviewSync writes the resolved preview record back into
messageAttachmentsMap with a fresh object identity on every run, and
`attachment` is in the effect's dependency array. useAttachments
re-derives `attachment` ({...db, ...liveEntry}) with a new identity on
every map write, so once polling resolves (pending -> ready on a loaded
conversation) the effect ping-pongs forever:
setAttachmentsMap -> re-derive -> effect -> setAttachmentsMap.

Only files large/slow enough to defer extraction are persisted at
status: 'pending', which is why small documents never triggered it.

Fix: an idempotency gate that bails before setAttachmentsMap when the
merged attachment already carries the resolved status/text/textFormat/
previewError. The write happens once and then settles.

Tests:
- useAttachmentPreviewSync.loop.spec.tsx wires the real
  useAttachments -> hook feedback to reproduce the loop (verified to
  throw #185 without the gate, settle with it).
- e2e/specs/mock/attachment-preview-loop.spec.ts loads a conversation
  with a pending code-exec attachment whose preview resolves ready and
  asserts the app does not crash.

Closes #13916

* 🔧 feat: Make Office Preview Extraction Cap Configurable (default 2MB)

The inline code-execution preview extraction ceiling was a hardcoded 1MB
constant (MAX_TEXT_EXTRACT_BYTES). Office/text artifacts over that skip
the inline preview and resolve to "Preview unavailable" (download-only).

Make it configurable via FILE_PREVIEW_MAX_EXTRACT_BYTES and raise the
default to 2MB so larger documents get an inline preview out of the box.
The rendered HTML remains independently capped at MAX_TEXT_CACHE_BYTES
(512KB), so image-heavy files over that still fall back to the existing
"preview too large" banner rather than rendering unbounded output.

- resolveMaxTextExtractBytes(env) parses the override, falling back to
  2MB on missing/non-numeric/non-positive values (warns on invalid).
- Documented in .env.example next to the other file-size limits.
- Unit tests cover default, valid override, fractional flooring, and
  invalid fallback.

* 🐛 fix: Guard sub-byte preview cap from flooring to zero

A fractional FILE_PREVIEW_MAX_EXTRACT_BYTES in (0, 1) passed the
positive-number check then floored to 0, making MAX_TEXT_EXTRACT_BYTES
zero and treating every non-empty artifact as oversized. Floor first,
then require the result to be >= 1 byte before accepting it; otherwise
fall back to the 2 MB default. Adds coverage for the sub-byte case.

*  test: Make exported-ceiling assertion env-independent

The "exported ceiling" assertion compared MAX_TEXT_EXTRACT_BYTES to a
literal 2 MB, but that const is initialized from
FILE_PREVIEW_MAX_EXTRACT_BYTES at module load — so the suite would
falsely fail when run with the override set. Assert the export tracks
resolveMaxTextExtractBytes(env) for the current environment instead; the
undefined-case test continues to pin the 2 MB default.
2026-06-23 16:34:43 -04:00
..
agents 🛡️ fix: Agent Permission Check on Image Upload Route (#12219) 2026-03-14 02:57:56 -04:00
code 🐛 fix: Prevent Infinite Render Loop on Code-Execution File Preview (#13922) 2026-06-23 16:34:43 -04:00
documents ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
encode 📎 fix: Preserve Gemini PDF Media Blocks (#13357) 2026-05-27 22:00:53 -07:00
mistral 🌐 fix: Centralize Outbound Proxy Handling (#13726) 2026-06-14 10:47:49 -04:00
audio.ts 🔧 fix: Upload Audio as Text missing Param (#9356) 2025-08-28 21:07:30 -04:00
context.ts 🗂️ fix: Scope Handoff Agent Context Docs (#13167) 2026-05-18 15:36:22 -04:00
filter.spec.ts 🖼️ feat: File Size and MIME Type Filtering at Agent level (#10446) 2025-11-10 21:36:48 -05:00
filter.ts 🖼️ feat: File Size and MIME Type Filtering at Agent level (#10446) 2025-11-10 21:36:48 -05:00
index.ts 🧯 fix: Harden Data Retention Semantics (#13049) 2026-05-19 21:58:42 -04:00
ocr.ts 🔍 refactor: OCR Fully Optional with Defaults for "Upload as Text" (#9856) 2025-09-26 11:56:11 -04:00
parse.ts ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
rag.spec.ts 🎨 chore: prettier --write all workspaces (#13281) 2026-05-23 17:52:58 -04:00
rag.ts 🎨 chore: prettier --write all workspaces (#13281) 2026-05-23 17:52:58 -04:00
retention.spec.ts 🗃️ feat: Retain Agent Files During All-Data Retention (#13477) 2026-06-02 15:04:10 -04:00
retention.ts ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
sweep.spec.ts 🧯 fix: Harden Data Retention Semantics (#13049) 2026-05-19 21:58:42 -04:00
sweep.ts ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
text.spec.ts 📝 fix: Preserve Raw Markdown Formatting on Upload as Text (#12734) 2026-04-19 19:31:39 -07:00
text.ts 📝 fix: Preserve Raw Markdown Formatting on Upload as Text (#12734) 2026-04-19 19:31:39 -07:00
validation.spec.ts 📄 feat: Model-Aware Bedrock Document Size Validation (#12467) 2026-03-30 16:50:10 -04:00
validation.ts 🧭 fix: Migrate Anthropic Long Context (#12911) 2026-05-02 22:14:19 +09:00