Repository navigation
fix: drag-and-drop document upload in browser mode (#728) - #729
Merged
Merged
Conversation
Drag-and-drop document upload fails with "Not found. The file or folder
may have been moved or deleted." because the frontend relied on the
Electron-only `File.path` property, which is `undefined` in all browsers
and was also removed in Electron 32+. The bare filename was then sent
to `/api/documents/upload-path`, which couldn't locate the file and
returned HTTP 404.
Add a new `POST /api/documents/upload` endpoint that accepts the file
content directly as multipart form data, streams it into
`~/.gaia/documents/<uuid>.<ext>` with an abort-on-overflow size check
(20 MB cap), dedupes by SHA-256 content hash, and indexes it through
the standard RAG pipeline. Partial and orphan files are cleaned up on
every failure path (ClientDisconnect, oversized, bad extension,
indexing failure, concurrent-drop race).
`DELETE /api/documents/{id}` now also unlinks the on-disk file when
it lives under the server-managed documents dir, while leaving
user-owned files from `upload-path` untouched.
Adds `ChatDatabase.get_document_by_hash()` for the dedup short-circuit
and 9 integration tests covering happy path, dedup, empty/oversized/
bad-extension rejection, indexing failure cleanup, and delete cleanup
for both server-owned and user-owned documents.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Document Library and ChatView drag-and-drop handlers relied on `(file as any).path` which is `undefined` in browsers and was removed in Electron 32+, causing drag-and-drop to fail with a confusing "Not found" error. Switch both handlers to the new `uploadDocumentBlob()` helper that POSTs the file content directly as multipart form data to `/api/documents/upload`. DocumentLibrary: refactor the shared upload state-machine (preflight/setUploadError/attach/refresh) into a `runUpload` helper so both the path-based flow (used by the folder input and file browser) and the new blob flow can share it cleanly. ChatView: swap the drag-drop handler to call `uploadDocumentBlob` directly; path-based upload is still used by the server-side file browser picker where a real filesystem path is available. Also fix the 404 error-message mapping in `getFriendlyError()` — it now prefers the backend's `detail` over the previous canned "The file or folder may have been moved or deleted." string, which was misleading for upload/indexing flows. 403 and 413 gain the same detail-aware treatment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document the two document upload endpoints (`upload-path` vs. `upload`) in `docs/spec/agent-ui-server.mdx`, explaining when each is used, how the blob endpoint streams to `~/.gaia/documents/` with hash-based dedup, and how server-owned files are cleaned up on delete. - Remove the per-PR concurrency group from the Claude workflow's `pr-comment` and `issue-handler` jobs so bursts of `@claude` mentions on different topics each run to completion instead of cancelling each other. These jobs only read diffs and post comments, so parallel runs are safe. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the `final_path.stat()` call for `file_mtime` inside the indexing try/except block so a rare filesystem hiccup between the atomic rename and the stat doesn't leak the final file. The stat failure will now trigger the same cleanup path as indexing/db failures, keeping the disk tidy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dropping a file onto the chat view was auto-opening the Document Library side panel, which interrupts the chat flow when the user just wants the file indexed and attached to the current session. Remove the `setShowDocLibrary(true)` call from the ChatView drop handler. The file still indexes via the blob upload endpoint and auto-attaches to the current session — users see the new doc appear in the session's attached-docs row without the panel popping open. The library can still be opened manually for progress on large files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous blob endpoint wrote files as pure UUID (e.g. ``7df3d195-...pdf``), which caused the agent LLM to hallucinate "cleaner" paths when referencing the document in tool calls — e.g. ``C:\Users\14255\.gaia\documents\<uuid>.pdf`` became ``/Users/14255/Documents/<uuid>.pdf`` in LLM-generated tool args, and the RAG tools' substring-based file lookup then failed with "Document not found in indexed documents." Embed the sanitized original stem in the final on-disk filename: ``<uuid>_<safe_stem>.<ext>``. The UUID prefix still guarantees uniqueness, and downstream consumers (RAG tools, the LLM) can now recognize the file by its human-readable name regardless of how the LLM transforms the surrounding path. `_sanitize_stem()` strips Windows-illegal characters (``<>:"/\|?*``), control characters, leading/trailing whitespace and dots, and caps length at 80 chars — keeping the total path well under Windows' 260-char MAX_PATH. Falls back to ``"file"`` if nothing usable remains after sanitization. Adds a test case for illegal-char sanitization and updates the happy-path test to assert the new filename pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full uuid4 (36 chars) is overkill for our use case — we already dedup blob uploads by SHA-256 content hash before writing, so identical- content collisions are impossible. The remaining concern is two concurrent different-content uploads drawing the same random ID, which at 12 hex chars (48 bits, 2.8e14 possibilities) has a collision probability of ~1 in 1.4e14 even with 1M concurrent uploads — effectively zero. Saves 24 chars off every managed filename, leaving more headroom under Windows' 260-char MAX_PATH limit. Example: before: ce5b8504-5baa-4a8a-89be-fe3b688bd762_my_document.pdf after: 9c59c981f8e7_my_document.pdf Introduces a small `_short_id()` helper so the length is defined in one place (``MANAGED_ID_LEN``) if we ever need to tune it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-applied by `util/lint.py --all --fix` (step 8: documentation version consistency check). Aligns the document allowlist across backend (`utils.py`, `files.py`), frontend (`UnsupportedFeature.tsx`), and tests so they all reference the same canonical set of supported extensions and reject formats that don't have a working extractor. Bundled into this PR per maintainer request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kovtcharov
marked this pull request as ready for review
April 9, 2026 22:07
The fixture patches `gaia.ui.routers.documents.MANAGED_DOCS_DIR` via its string path, so the symbol no longer needs to be imported at the top of the test file. Resolves the F401 flake8 error blocking CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kovtcharov
enabled auto-merge
April 10, 2026 03:22
itomek
approved these changes
Apr 10, 2026
Merged
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Apr 10, 2026
Release notes for v0.17.2. See [docs/releases/v0.17.2.mdx](https://github.com/amd/gaia/blob/v0.17.2-release/docs/releases/v0.17.2.mdx) for the full body. **Headline features:** - One-click desktop installers for Windows, macOS, and Linux (#731 + #743) - Customizable agents and Builder Agent (#720) - MCP tool execution visibility in Agent Activity panel (#717) **Security:** RAG cache integrity verification (#722, closes #447) **Bug fixes:** drag-and-drop upload in any browser (#729), Linux install fixes (#743) **Release & CI:** unified publish workflow (#715), Claude review on fork PRs (#685), npm OIDC fix (#683), webui package.json sync (#682) Full changelog: v0.17.1...v0.17.2 --------- Co-authored-by: Tomasz Iniewicz <tomasz@iniewicz.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #728.
What was broken
Drag-and-drop document upload failed with "Indexing error: Not found. The file or folder may have been moved or deleted." in any browser, and in the packaged Electron desktop app (Electron 40).
Root cause:
src/gaia/apps/webui/src/components/DocumentLibrary.tsx:265relied on(file as any).path, an Electron-only augmentation that'sundefinedin every browser and was removed in Electron 32+. The fallbackfile.name(bare filename, no directory) was POSTed to/api/documents/upload-path, which couldn't resolve it → HTTP 404 → misleading frontend error.Fix
New
POST /api/documents/uploadendpoint that accepts the file content as multipart form data, streams it to~/.gaia/documents/<shortid>_<original_name>.<ext>with a 20 MB cap, dedupes by SHA-256 hash, and indexes via the standard RAG pipeline. Drag-and-drop in both DocumentLibrary and ChatView now use this endpoint. Delete cleans up server-owned files; user-owned files fromupload-pathare left alone.Also fixes
getFriendlyError()inapi.tsto prefer the backend'sdetailover the canned 404 string, so future upload errors surface meaningful messages.Details worth knowing
<12-hex-id>_<sanitized_name>.<ext>) so the agent LLM and RAG tools can recognize the file by its human-readable name. A pure-UUID filename caused the LLM to hallucinate paths (e.g.C:\Users\14255\.gaia\documents\<uuid>.pdf→/Users/14255/Documents/<uuid>.pdf) and fail tool lookups.<>:"/\|?*), control chars, leading/trailing whitespace and dots, and caps length at 80 chars — keeps total path well under Windows MAX_PATH.os.replace, orphan cleanup on indexing/db/race failures.Bundled cleanup
This PR also includes a doc-consistency pass auto-applied by
util/lint.py --all --fix(step 8) that aligns the supported-extension allowlist across backend (utils.py,files.py), frontend (UnsupportedFeature.tsx), and tests.What's NOT changed
POST /api/documents/upload-pathis untouched — still used by the server-side file browser inFileBrowser.tsx, where the user explicitly picks a real filesystem path.webUtils.getPathForFilesince it's broken for drag-drop in Electron 30-33+ and the blob path works everywhere.Known limitation / follow-up
The agent LLM still occasionally cites the on-disk filename (e.g.
9c59c981f8e7_report.pdf) when answering questions about a document, instead of the user's display name (report.pdf). This is becausesrc/gaia/agents/chat/tools/rag_tools.pyreturns chunk metadata using raw filepaths fromself.rag.indexed_filesrather than translating them to the display name stored indocuments.filename. This is a separate, pre-existing concern in the RAG tool layer and fixing it requires a focused refactor ofrag_tools.py(~1800 lines, touchesquery_documents,query_specific_file,summarize_document,search_indexed_chunks,list_indexed_documents). Tracking it as a follow-up rather than expanding this PR's scope.Conflict notes for reviewers
src/gaia/ui/routers/documents.py, but edits the body ofupload_by_path— this PR only adds new top-of-file constants, helpers, and a newupload_document_blobfunction inserted afterupload_by_path. Three-way merge should be clean. Semantically, fix: surface document indexing failures instead of silent 0-chunk success #723's "raise on failure" change is more correct under my new endpoint than the previous silent-0-chunk behavior.ChatView.tsxin a disjoint line range. No conflict.Steps to reproduce the bug (before this fix)
One-time setup
Start the services
Reproduce
documents..pdf/.txt/.md/.docxfrom your file manager onto the Document Library drop zone or the chat area.POST /api/documents/upload-path→ HTTP 404{"filepath":"report.pdf"}(bare filename — the smoking gun)HTTPException: File not found: report.pdfVerify the fix
Repeat the drop. Expected:
POST /api/documents/upload(multipart, notupload-path) → HTTP 200~/.gaia/documents/<shortid>_report.pdfwith the original name embeddedBlob upload indexed: report.pdf (<N> bytes, <M> chunks)Blob upload dedup: report.pdf matches existing doc <id>(no second file on disk)Test plan
tests/integration/test_documents_router.py): happy path, dedup, empty/oversized/bad-extension/illegal-chars rejection, indexing failure cleanup, delete unlinks server-owned, delete preserves user-owned, 404 on deletepython util/lint.py --all --fixcleantsc --noEmitclean (no new errors in changed files)