LibreChat/api/server/services/Files
Danny Avila 224512851a
🗂️ fix: Resolve Code Interpreter Destination Collisions (#15449)
* 🗂️ fix: Resolve Code Interpreter Destination Collisions

Codeapi mounts each `/exec` input at a destination derived from its `name`
and rejects the whole request when two entries land on one. LibreChat never
enforced that: `primeFiles` sent `name: file.filename` verbatim, and the
graph seed deduped by `(storage_session_id, id)` — identity, not destination.

Two conversation files sharing a filename therefore killed code execution for
the rest of the conversation. The rejected request never reaches the sandbox,
so no result comes back to collapse the pair, and every later turn re-primes
both and fails identically. Only code-generated outputs are covered by the
`(filename, conversationId, context, tenantId)` partial unique index; uploads
carry `context: message_attachment`, so one conversation can hold several
records sharing a filename — two uploads of `image.png`, or an upload whose
name a later execution wrote back in place.

`packages/api/src/files/code/destinations.ts` now owns the name → destination
mapping for every contributor to an `/exec` file list, mirroring codeapi's
conflict rule including directory-prefix conflicts:

- `primeFiles` claims per pushed file, disambiguating with a `-2`, `-3`
  counter and echoing the resolved path into the tool context, so the model
  is told where each file actually mounts. Claim order comes from immutable
  `createdAt`, not `getFiles`'s `updatedAt` sort, which usage accounting and
  re-upload both bump — order-dependent destinations would silently repoint
  a path an earlier turn had already handed the model. Newest wins the bare
  name, matching `ToolNode.updateCodeSession` and giving an in-place rewrite
  precedence over the original it superseded.
- `seedCodeFilesIntoSessions` drops a later file whose destination is already
  claimed. Renaming there would invent a path nothing told the model about,
  and a conflict at that layer means one logical file arrived twice under
  different storage pointers.

Fixes #15443

* 🧹 chore: Tolerate Holes in the Destination Priority Sort

`primeFiles` sorts the raw `getFiles` result before its own per-entry guard
runs, so the comparator has to survive a null row rather than throw ahead of
the loop that was already defending against one.

Also tightens the tool-context assertion in the upload-vs-output case, which
was passing on a trailing space rather than on the path.

* 🔁 fix: Address Codex Review on Destination Assignment

Four findings from the review of `b0f61d0`.

**Identity-derived suffixes instead of a collision counter.** A counter is
assigned from whatever is in the set, so a third file arriving under the
literal name a displaced file had been given pushed that file along, and code
the model wrote in an earlier turn would silently start reading the newcomer.
The fallback now hashes the file's own `file_id`, so a displaced file keeps
its destination for the life of the conversation and only the winner of the
bare name can change. A trailing counter still guards a hash collision so the
search terminates on any input.

**Rank by last content write, not record creation.** `processCodeOutput`
claims one row per `(filename, conversationId)` and rewrites it in place, so
a repeatedly written output keeps its original `createdAt` while recording the
latest writer in `metadata.sourceDispatchedAt`. Ranking on `createdAt` alone
handed the bare path to an upload the output had since been rewritten over.
Recency is now `max(sourceDispatchedAt, createdAt)` — still never `updatedAt`,
which usage accounting bumps without any content changing.

**Advertise a displaced generated output.** Generated files are left out of
the tool context because the model already knows what it wrote — which stops
being true once a newer same-named file takes the bare path and the output
mounts somewhere the model has never seen. Displaced outputs are now listed
with the name they were written as, and displaced uploads name their original
too.

**Rank conversation files above an agent's private ones.** Each agent claims
destinations over its own candidate set — the conversation's files plus its
own — so a private file taking a bare name from a shared file in one agent but
not in another left two agents advertising different paths for the same file
into one shared mount namespace. Shared files now rank first, so the majority
that every agent sees resolves identically. The private-versus-private case
still drops at the merge and is documented there; closing it needs one
assignment across contributors, made before any agent renders its context.

* 📌 test: Pin Alias Ownership on an In-Place Rewrite

Codex round 2 flagged that the identity alias still falls back to a counter
when the alias itself is literally claimed, so a displaced file can move after
all. The mechanism is real and my JSDoc overstated the guarantee, which this
corrects.

The behaviour stays as it is. The realistic way a file arrives under another
file's alias is the model rewriting the displaced file in place: it was told
`<stem>-<hash>.ext`, wrote back to it, and `processCodeOutput` registered an
output under that literal name. Keeping the alias with the newest holder is
what makes the model's familiar path resolve to its own edit; reserving it
against the newcomer would hand that path back to the bytes it replaced. The
test pins that ordering so a later change cannot invert it silently.
2026-09-01 00:55:54 -04:00
..
Audio 🛂 feat: Filter Model-Bound Content by Source (#14425) 2026-08-21 22:43:32 -04:00
Azure 🪣 refactor: Rate-Limit Token Routes and Cap Remote File Downloads (#13978) 2026-06-26 12:19:03 -04:00
Citations
Code 🗂️ fix: Resolve Code Interpreter Destination Collisions (#15449) 2026-09-01 00:55:54 -04:00
Firebase 🪣 refactor: Rate-Limit Token Routes and Cap Remote File Downloads (#13978) 2026-06-26 12:19:03 -04:00
images 🖍️ fix: Honor Agent Image Detail Settings (#15407) 2026-08-31 08:34:05 -04:00
Local 🪣 refactor: Rate-Limit Token Routes and Cap Remote File Downloads (#13978) 2026-06-26 12:19:03 -04:00
OpenAI
VectorDB
index.js
permissions.js 🧱 fix: Enforce Agent Runtime File Trust Boundaries (#14577) 2026-08-02 14:18:28 -04:00
permissions.spec.js 🧱 fix: Enforce Agent Runtime File Trust Boundaries (#14577) 2026-08-02 14:18:28 -04:00
process.integration.spec.js
process.js 🛂 feat: Filter Model-Bound Content by Source (#14425) 2026-08-21 22:43:32 -04:00
process.spec.js 🛂 feat: Filter Model-Bound Content by Source (#14425) 2026-08-21 22:43:32 -04:00
retention.js 📨 feat: Continue Bound Child Agents from Events (#15112) 2026-08-23 01:15:57 -04:00
retention.spec.js 📨 feat: Continue Bound Child Agents from Events (#15112) 2026-08-23 01:15:57 -04:00
strategies.js