[GH-ISSUE #5717] [BUG]: /api/v1/document/upload silently drops 'metadata' field in Desktop 1.13.0 (arg-count mismatch + nested payload key) #5254

Closed
opened 2026-06-05 14:52:57 -04:00 by yindo · 2 comments
Owner

Originally created by @magnushasselquist on GitHub (May 28, 2026).
Original GitHub issue: https://github.com/Mintplex-Labs/anything-llm/issues/5717

Originally assigned to: @angelplusultra on GitHub.

How are you running AnythingLLM?

AnythingLLM Desktop app — macOS 1.13.0 (CFBundleVersion 1.13.0).

What happened?

Posting to /api/v1/document/upload with a metadata multipart field is documented to override the collector's defaults for title, docAuthor, description, and docSource. Instead, the supplied metadata is silently discarded and the stored document always falls back to defaults.

Repro (after creating an API key in Settings → API Keys and a workspace probe):

echo "hello world" > /tmp/probe.txt
curl -sS -X POST \
  -H "Authorization: Bearer $ALLM_API_KEY" \
  -F "file=@/tmp/probe.txt" \
  -F "addToWorkspaces=probe" \
  -F 'metadata={"title":"Custom","docAuthor":"my-tool","docSource":"local://probe.txt"}' \
  http://localhost:3001/api/v1/document/upload | jq

Returns:

{
  "documents": [{
    "title": "probe.txt",
    "docAuthor": "Unknown",
    "docSource": "a text file uploaded by the user.",
    "chunkSource": "localfile://[object Object]"
  }]
}

Note chunkSource: "localfile://[object Object]" — a tell that the metadata object reached some code path but was stringified incorrectly.

Root cause (verified against the Desktop 1.13.0 bundle)

Two independent off-by-N bugs in the Desktop build that compound:

1. CollectorApi.processDocument signature changed but the API endpoint wasn't updated.

/Applications/AnythingLLM.app/Contents/Resources/backend/server.js:

// CollectorApi
async processDocument(filename = "", localPath = null, metadata = {}) {
  const data = JSON.stringify({
    filename,
    options: { ...this.#attachOptions(), localPath, metadata }
  });
  // ...
}

The signature is (filename, localPath, metadata) — 3 args. The internal GUI upload path calls it correctly with processDocument(originalname, file.localPath). But the public API handler calls it with only 2:

// /v1/document/upload handler in the Desktop bundle
const { addToWorkspaces = "", metadata = {} } = reqBody(request);
const parsedMetadata = typeof metadata === "string" ? safeJsonParse(metadata, {}) : metadata;
// ...
await Collector.processDocument(originalname, parsedMetadata);
//                                              ^ lands in `localPath` slot,
//                                                real `metadata` arg = undefined → {}

So the user's metadata object is sent as options.localPath and the collector never receives a metadata field.

2. Payload key moved into options.metadata but the collector still reads top-level metadata.

The Desktop bundle's processDocument nests metadata under options.metadata, but collector/index.js continues to destructure metadata from the top-level request body:

// collector /process handler
const { filename, options = {}, metadata = {} } = reqBody(request);
//                              ^ top-level — but server sends it under options
return await processSingleFile(filename, options, metadata);

Even if bug #1 were fixed (passing metadata as the 3rd arg), the collector would still read body.metadata = undefined because it's nested under options.metadata in the Desktop's payload shape.

Comparison with main

On main (server/utils/collectorApi/index.js and server/endpoints/api/document/index.js as of the latest commit), both ends are consistent:

  • processDocument(filename, metadata) — 2 args
  • Payload puts metadata at top level
  • Collector reads metadata from top level

So this bug is isolated to the Desktop bundle. Looks like a refactor that introduced localPath propagation for the internal GUI upload was bundled without aligning the public API handler or the collector's payload contract.

Suggested fix

Either:

  • A. Restore the 2-arg API endpoint call site to match the 3-arg signature: pass null as localPath and parsedMetadata as the third arg in both /v1/document/upload and /v1/document/upload/:folderName handlers. And change the payload shape in processDocument to put metadata at top level (matching what the collector reads).
  • B. Drop the 3-arg overload entirely in the Desktop bundle and resync with main's signature.

Impact

Any tool calling /api/v1/document/upload on Desktop loses the ability to:

  • Set citation-friendly docSource (e.g. original folder breadcrumbs for files synced from a local folder)
  • Tag docs with a stable docAuthor for later identification (e.g. "documents managed by tool X")
  • Provide accurate title / description overrides

Related: this is the same upload endpoint that #1886's commenters (e.g. @jstawski for a SharePoint sync) said couldn't accept metadata. The metadata feature appears to work on main and to be broken specifically in Desktop builds.

Are there known steps to reproduce?

Yes — see the curl above. Reproduced 100% on Desktop 1.13.0 / macOS.

Originally created by @magnushasselquist on GitHub (May 28, 2026). Original GitHub issue: https://github.com/Mintplex-Labs/anything-llm/issues/5717 Originally assigned to: @angelplusultra on GitHub. ### How are you running AnythingLLM? AnythingLLM Desktop app — macOS 1.13.0 (CFBundleVersion 1.13.0). ### What happened? Posting to `/api/v1/document/upload` with a `metadata` multipart field is documented to override the collector's defaults for `title`, `docAuthor`, `description`, and `docSource`. Instead, the supplied metadata is silently discarded and the stored document always falls back to defaults. Repro (after creating an API key in Settings → API Keys and a workspace `probe`): ```bash echo "hello world" > /tmp/probe.txt curl -sS -X POST \ -H "Authorization: Bearer $ALLM_API_KEY" \ -F "file=@/tmp/probe.txt" \ -F "addToWorkspaces=probe" \ -F 'metadata={"title":"Custom","docAuthor":"my-tool","docSource":"local://probe.txt"}' \ http://localhost:3001/api/v1/document/upload | jq ``` Returns: ```json { "documents": [{ "title": "probe.txt", "docAuthor": "Unknown", "docSource": "a text file uploaded by the user.", "chunkSource": "localfile://[object Object]" }] } ``` Note `chunkSource: "localfile://[object Object]"` — a tell that the metadata object reached *some* code path but was stringified incorrectly. ### Root cause (verified against the Desktop 1.13.0 bundle) Two independent off-by-N bugs in the Desktop build that compound: **1. `CollectorApi.processDocument` signature changed but the API endpoint wasn't updated.** `/Applications/AnythingLLM.app/Contents/Resources/backend/server.js`: ```js // CollectorApi async processDocument(filename = "", localPath = null, metadata = {}) { const data = JSON.stringify({ filename, options: { ...this.#attachOptions(), localPath, metadata } }); // ... } ``` The signature is `(filename, localPath, metadata)` — 3 args. The internal GUI upload path calls it correctly with `processDocument(originalname, file.localPath)`. But the public API handler calls it with only 2: ```js // /v1/document/upload handler in the Desktop bundle const { addToWorkspaces = "", metadata = {} } = reqBody(request); const parsedMetadata = typeof metadata === "string" ? safeJsonParse(metadata, {}) : metadata; // ... await Collector.processDocument(originalname, parsedMetadata); // ^ lands in `localPath` slot, // real `metadata` arg = undefined → {} ``` So the user's metadata object is sent as `options.localPath` and the collector never receives a `metadata` field. **2. Payload key moved into `options.metadata` but the collector still reads top-level `metadata`.** The Desktop bundle's `processDocument` nests metadata under `options.metadata`, but `collector/index.js` continues to destructure `metadata` from the top-level request body: ```js // collector /process handler const { filename, options = {}, metadata = {} } = reqBody(request); // ^ top-level — but server sends it under options return await processSingleFile(filename, options, metadata); ``` Even if bug #1 were fixed (passing metadata as the 3rd arg), the collector would still read `body.metadata = undefined` because it's nested under `options.metadata` in the Desktop's payload shape. ### Comparison with `main` On `main` (`server/utils/collectorApi/index.js` and `server/endpoints/api/document/index.js` as of the latest commit), both ends are consistent: - `processDocument(filename, metadata)` — 2 args - Payload puts `metadata` at top level - Collector reads `metadata` from top level So this bug is isolated to the Desktop bundle. Looks like a refactor that introduced `localPath` propagation for the internal GUI upload was bundled without aligning the public API handler or the collector's payload contract. ### Suggested fix Either: - **A.** Restore the 2-arg API endpoint call site to match the 3-arg signature: pass `null` as `localPath` and `parsedMetadata` as the third arg in both `/v1/document/upload` and `/v1/document/upload/:folderName` handlers. **And** change the payload shape in `processDocument` to put `metadata` at top level (matching what the collector reads). - **B.** Drop the 3-arg overload entirely in the Desktop bundle and resync with `main`'s signature. ### Impact Any tool calling `/api/v1/document/upload` on Desktop loses the ability to: - Set citation-friendly `docSource` (e.g. original folder breadcrumbs for files synced from a local folder) - Tag docs with a stable `docAuthor` for later identification (e.g. "documents managed by tool X") - Provide accurate `title` / `description` overrides Related: this is the same upload endpoint that #1886's commenters (e.g. @jstawski for a SharePoint sync) said couldn't accept metadata. The metadata feature appears to work on `main` and to be broken specifically in Desktop builds. ### Are there known steps to reproduce? Yes — see the curl above. Reproduced 100% on Desktop 1.13.0 / macOS.
yindo added the investigating label 2026-06-05 14:52:57 -04:00
yindo closed this issue 2026-06-05 14:52:57 -04:00
Author
Owner

@magnushasselquist commented on GitHub (May 29, 2026):

Thanks for the quick triage. Just to make sure we're tracking correctly: is the fix landing in an upcoming Desktop release (the issue reproduces on 1.13.0), or is the resolution that current main already has the correct signature and Desktop will pick it up on the next build?

Happy to re-verify on a newer Desktop build whenever it ships — just want to know whether to re-open if it persists on 1.13.1.

<!-- gh-comment-id:4573153674 --> @magnushasselquist commented on GitHub (May 29, 2026): Thanks for the quick triage. Just to make sure we're tracking correctly: is the fix landing in an upcoming Desktop release (the issue reproduces on 1.13.0), or is the resolution that current `main` already has the correct signature and Desktop will pick it up on the next build? Happy to re-verify on a newer Desktop build whenever it ships — just want to know whether to re-open if it persists on 1.13.1.
Author
Owner

@timothycarambat commented on GitHub (May 29, 2026):

Ah yes, correct - it is merged but not yet live and will be in the next patch, which is planned to be 1.13.1!

<!-- gh-comment-id:4579526287 --> @timothycarambat commented on GitHub (May 29, 2026): Ah yes, correct - it is merged but not yet live and will be in the next patch, which is planned to be 1.13.1!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Mintplex-Labs/anything-llm#5254