Skip to content

fix(ui): cite chat attachments by their own name - #6896

Merged
kovtcharov-amd merged 3 commits into
amd:mainfrom
ityashag:fix/attachment-citation-name
Nov 7, 2026
Merged

kovtcharov-amd merged 3 commits into
amd:mainfrom
ityashag:fix/attachment-citation-name

Conversation

@ityashag

@ityashag ityashag commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Answers about a file attached with the + button now cite the file's own name instead of GAIA's internal upload ID: According to kestrel-plan.pdf, page 1: rather than According to f27f4885-cec8-4161-9dbd-ebc8a3c25005.pdf, page 1:.

Why

The agent only ever saw the upload copy's random name, so that's what it cited, and users couldn't tell which file an answer came from.

Linked issue

Closes #5183

Changes

  • Adds src/gaia/upload_names.py, the one place that knows the naming. Attachments are stored as <12 hex>_<name><ext>, and the prefix is removed for display only when the file is directly inside chat/uploads.
  • upload_file uses the new name. /api/files/upload still returns filename, original_name and url, but filename is no longer a UUID.
  • query_specific_file cites the readable name in its instruction and messages. file and source_files keep the exact path, so the file can still be opened.
  • The chunk cards and the "Indexed ..." step label use the readable name.

Decisions to check

  • Not _sanitize_stem: the triage comment suggested reusing it from documents.py, but it keeps spaces and parentheses. UPLOAD_LINK_RE stops at a space or ), and UPLOAD_URL_RE in markdown.ts only serves ASCII letters, digits, ., _ and -, so Q3 plan (final).pdf would stop working as a link. This keeps only those characters.
  • Prefix, not suffix: the name always starts with a hex character, which UPLOAD_URL_RE requires. <uuid>.pdf copies from before this change don't match, so they keep their full name.
  • Random ID length: 48 bits instead of the UUID's 122, the same as managed documents.

Limits

  • Spaces and punctuation become _, so Q3 plan (final).pdf is cited as Q3_plan_final.pdf.
  • A name with no ASCII letters or digits is cited as attachment.pdf. The original name still appears on the link and in the turn summary.
  • query_documents still asks for the full path, as it does for every document. That path now ends in a readable name, with the prefix.
  • Uploads made before this change keep their UUID names.

Test plan

Run in a clean python:3.12-slim container, with .[ui] and the test tools installed:

  • pytest tests/unit/chat/ui/test_upload_names.py tests/unit/chat/ui/test_upload_links.py tests/unit/chat/ui/test_attachment_scope.py tests/unit/chat/ui/test_upload_serving.py tests/unit/chat/ui/test_media_upload.py tests/unit/chat/ui/test_sse_handler.py tests/unit/chat/ui/test_turn_summary.py tests/unit/test_rag_citation_instruction.py tests/unit/test_rag_file_resolution.py tests/integration/test_files_router.py: 604 passed, 1 skipped.
  • black --check and isort --check-only on the changed files: clean.
  • Full pytest tests/unit: 35,021 passed. It was run with .[ui] only and without git history, so it also had 96 failures and 523 errors. Many were missing test tools. I reran the 95 affected test files with those tools installed: 25 failures, none in the files this PR changes. Most also fail on main, and the rest need git history, which my container's copy didn't have.

Not run: python util/lint.py --all, the web UI tests, and a real model. The last means this confirms the instruction sent to the model, not a model's actual answer.

Evidence

The same upload and query_specific_file steps, on main and on this branch:

Attached file Cited on main Cited on this branch Stored on this branch
kestrel-plan.txt 368b4401-2336-47a7-9c69-31a64dce33ee.txt kestrel-plan.txt 25da2dcd4869_kestrel-plan.txt
Q3 plan (final).txt 38152cd4-d404-4bb2-a928-d9cd8cc22a64.txt Q3_plan_final.txt 19bab3a67092_Q3_plan_final.txt
-draft.txt cc50ebe6-f0d4-4c66-9c15-0acbd1800208.txt draft.txt 174b21ed143a_draft.txt
تقرير.txt 08688a4d-3d5a-4a81-91d2-c6a5305e210c.txt attachment.txt 5ac458ed0c96_attachment.txt

The step label changes from Indexed <uploads>/368b4401-….txt (1 chunks) to Indexed kestrel-plan.txt (1 chunks). On both versions, every file still resolves through the agent's link handling and matches UPLOAD_URL_RE.

  • Agent exposed in the Agent UI: N/A, no agent changes. The citation and step label are covered by the evidence above, without a running UI or model.
  • MCP tools / servers: N/A
  • CLI: N/A
  • HTTP API / REST: /api/files/upload returns the new filename form, shown above.

Checklist

  • I have linked a GitHub issue above (Closes #N / Fixes #N / Refs #N).
  • I have described why this change is being made, not just what changed.
  • I have run linting and tests locally (python util/lint.py --all, pytest tests/unit/). Partly: black, isort and pytest tests/unit/ as described above, not util/lint.py.
  • I have attached real-world evidence matched to the surface I changed (see Evidence above), or marked each surface N/A.
  • I have updated documentation if user-visible behavior changed (see CONTRIBUTING.md). N/A: I found no docs describing the upload file name.

@github-actions github-actions Bot added tests Test changes agents labels Nov 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Verdict: Approve with suggestions

Answers about a file attached in chat will now cite a readable form of the file's name, such as kestrel-plan.pdf, instead of GAIA's random upload ID. The change is small and contained. It fails safe: a file outside the uploads folder, or one uploaded before this change, keeps its full name. Nothing blocks the merge.

  • Follow-up (not blocking): a name with spaces or punctuation is still cited in a changed form (Q3 plan (final).pdf shows as Q3_plan_final.pdf). Arabic and other non-Latin names are all cited as attachment.pdf. The exact original name is already in each attachment link's label, so citations could use it. Please file an issue for this if you'd rather not fix it here.

Real-world evidence

N/A here: no evidence bundle was produced for this run. Part of this verdict rests on static review alone. The PR description has its own evidence: a main-vs-branch table of upload names and citations, and the upload endpoint's new filename form. It says no real model was run, so it proves the instruction sent to the model, not the answer a model gives. Nobody has seen the step label or chunk cards in the running Agent UI yet; that screenshot is still owed, but it doesn't block.

🔍 Technical details

Issues

🟡 Follow-up: cite the user's exact name, not the sanitized stem (src/gaia/upload_names.py:20, src/gaia/agents/tools/rag_tools.py:1478)
display_file_name can only recover the ASCII-sanitized stem, so citations change the user's name and turn every non-Latin name (the Arabic UI's users) into attachment.pdf. upload_display_names() in turn_summary.py already maps copy name → original name from the composer's link label. Passing that map into the citation instruction would cite the real name. Suggested issue title: "Cite chat attachments by their exact original name, including non-ASCII names".

🟢 Stale comment (src/gaia/ui/routers/files.py:241): "Generate unique filename preserving original extension" now also keeps the name.

    # Unique copy name: random ID, sanitized original name, original extension

Checks run outside the diff

  • Sibling consumers of upload names: UPLOAD_LINK_RE/resolve_upload_links, attached_upload_paths (permission-free read of the copy), UPLOAD_URL_RE in markdown.ts, and upload_display_names. None assumes a UUID stem, and the new name matches every pattern (the parametrized hostile-names test covers this end to end).
  • uuid is still used in files.py (media names) and in test_files_router.py, so neither import is left dangling.
  • upload_original_name only strips the prefix directly under chat/uploads, which matches uploads_dir() (<GAIA_HOME>/chat/uploads). User files with a look-alike prefix elsewhere keep their name, and the tests cover that.
  • Docs drift: no doc or OpenAPI text describes upload copies as UUID-named (the only "UUID-based" mention is in tunnel.py, which has nothing to do with uploads).
  • Security: the stem is reduced to [A-Za-z0-9._-], with path separators split off first and leading dots stripped. Traversal, Markdown-link breaking and shell metacharacters are all neutralized. No concern.

Strengths

  • The naming rules live in one small module, used by the router, the RAG tool and the SSE handler, so they can't drift apart.
  • The tests cover the whole path a user takes: hostile names through upload → link resolution → permission scope → citation, plus the guard that leaves older uploads and the user's own files unchanged.
  • file and source_files keep the exact path, so only the display name changes, and the file can still be opened.

@kovtcharov-amd
kovtcharov-amd added this pull request to the merge queue Nov 7, 2026
Merged via the queue into amd:main with commit b169b7f Nov 7, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ux(ui): document answers cite the upload's internal ID instead of the attached file's name

2 participants