Skip to content

PR #15684 — failed hold release leaves an attached Project file TTL-armed (discussion_r3951336169) #12

Closed

Description

@berry-13

Deferred residual from upstream PR LibreChat-AI#15684 (head 2dbd5e7e63).
Finding: LibreChat-AI#15684 (comment)

What happens

addChatProjectFile associates a file with a Chat Project through one atomic findOneAndUpdate (capacity guarded), and only afterwards clears the upload hold on the file document:

// packages/data-schemas/src/methods/chatProject.ts
const updated = await ChatProject.findOneAndUpdate(
  { _id: project._id, user, file_ids: { $ne: fileId },
    [`file_ids.${MAX_CHAT_PROJECT_FILES - 1}`]: { $exists: false } },
  { $addToSet: { file_ids: fileId }, $inc: { contextRevision: 1 } },
  ...
);
if (updated) {
  await releaseTemporaryHold();   // $unset expiresAt, temp_file_id
  return updated;
}

That ordering is deliberate: a racer that loses the capacity guard must keep its hold so the sweep can reclaim the upload — that is what the reviewed finding asked for. The two writes span two collections without a transaction, so if the release write fails after the association commits, the project keeps the reference while the file keeps expiresAt, a MongoDB-managed one hour TTL (packages/data-schemas/src/schema/file.ts). The request surfaces as a 500 from addProjectFile, and the file can be deleted underneath a project that still lists it.

Why it is not already broken

An idempotent re-add repairs it: the atomic update no-ops, the fresh read finds the file already associated, and the release is retried. The client retries the association on its failed row, so the common path self-heals. A user who never retries keeps a project reference that resolves as unavailable after the TTL fires.

Suggested direction

Compensate the release rather than relying on a retry: a bounded inline retry, or a reconciliation pass that clears holds on files already referenced by a project (the sweep is expiredAt-driven and does not look at project membership). Worth a regression that forces the release write to fail and asserts the file does not stay TTL-armed.

No regression covers this window today.

Activity

  1. added
    needs triagereviewctl followups state
    bugSomething isn't working
    and removed
    needs triagereviewctl followups state
    on Sep 17, 2026
  2. berry-13 commented on Sep 29, 2026

    @berry-13
    OwnerAuthor

    Fixed in the root PR at LibreChat-AI/LibreChat@d92c40c8ce (a failed hold release now fails the attach with nothing attached), verified on head 2f79ee5. Closes when LibreChat-AI#15684 merges.

  3. added
    readyreviewctl followups state
    and removed on Sep 30, 2026
  4. berry-13 commented on Oct 8, 2026

    @berry-13
    OwnerAuthor

    Fixed on dev (852e004): packages/data-schemas/src/methods/chatProject.ts:574-640 releases the hold and restores it via restoreTemporaryHold on failure (d92c40c, LibreChat-AI#15684), so a failed release no longer leaves an attached file TTL-armed.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: packagesreviewctl followups statebugSomething isn't workingreadyreviewctl followups state

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions