You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A stored .docx contributes nothing — extractFileText handles text and PDF only, so read_file and RAG see an empty document (and .doc is a different, harder problem) #763
A .docx that reaches the file store is invisible to the agent. read_file refuses it, search_knowledge cannot retrieve from it, and the console shows a file with no content. The agent can say "there is a file called X" and nothing else about it.
That is the first wall in #756: the user's emailed club form arrives, gmail_download_attachment stores it (once #755 is fixed), and the agent still cannot tell the user a single thing the form asks for.
Priority — P2: correctness: the platform accepts and stores the file, then reports it as unreadable. Nothing is silently wrong, but a stored document contributing nothing to RAG is a real gap in a store that advertises document-grounded chat.
Where it is — VERIFIED
workers/api/src/agent-storage-utils.ts:133-149 is the whole extraction dispatch:
Two formats, then a catch-all. A .docx takes the last line.
Downstream, read_file then fails honestly — workers/api/src/lib/storage-tools.ts:365-369:
constfile=awaitengine.fileGet(id);if(!file)returnfail(call.name,`File not found: ${id}`);if(!file.meta.mimeType.startsWith("text/")&&!/json|xml|csv|html/.test(file.meta.mimeType)){returnfail(call.name,`${file.meta.name} is ${file.meta.mimeType} and has no extracted text — its content is not readable as text`);}
The honesty is right and should stay; the gap is that there is nothing for it to read.
Absence checked:grep -rn "docx\|msword\|officedocument\|DecompressionStream" workers/api/src packages/sdk/src packages/browser-runner/src | grep -v '\.test\.' returns six hits — two are the MIME-type lookup table (storage-tools.ts:815-816), two are DecompressionStream used for PDF FlateDecode (agent-storage-utils.ts:200) and repo-tarball gzip (repo-ingest.ts:205), one is a comment in resume-parse.ts:52 ("Claude reads PDFs natively; skip other formats (docx/txt) for now — but SAY so"), and two are accept=".pdf,.txt,.doc,.docx" in browser-runner test fixtures. No ZIP reader exists anywhere in the tree — grep -rn "deflate-raw\|central directory\|centralDirectory" workers/api/src packages/*/src returns nothing.
.docx and .doc are two different problems — do not conflate them
This is the distinction #756 collapsed, and it decides how much of this ticket is even possible.
.docx
.doc
Container
ZIP (deflate members)
OLE2 / CFB compound binary
Text location
word/document.xml — XML with <w:t> runs
FAT-chained sectors, format-version-dependent, some parts compressed with a bespoke scheme
Tractable in a Worker?
Yes
Realistically no
The primitives for .docx already exist in this runtime: DecompressionStream is used twice today (agent-storage-utils.ts:200, repo-ingest.ts:205), and repo-ingest.ts:245 (readTar, "Minimal ustar/GNU tar reader") is the direct precedent for a hand-rolled archive walker in this codebase. A ZIP central-directory reader plus one DecompressionStream("deflate-raw") per member gets word/document.xml; stripping <w:t> runs and inserting a newline per <w:p> gets readable text.
.doc has no comparable path and no dependency in this tree can be added for it that runs on workerd. The user's actual attachment in #756 is the .doc. Scope this ticket to .docx and let #756's parent carry the decision about .doc.
What to do — cheapest first
Say what it is, instead of nothing. In extractFileText, return status:"unsupported" with an error naming the format — a .doc/.docx should produce "this is a Word document; the platform cannot read it yet", not silence. The ExtractedFileText.error field already exists (agent-storage-utils.ts:127-131). Ships in isolation and is honest either way.
.docx text extraction. ZIP member reader → word/document.xml → text. Keep it pure and next to extractPdfText, with the same size cap discipline. Once it returns text, read_file, search_knowledge and the Index panel all light up with no further change, because they all read fileGetText.
Backfill is not automatic. Extraction happens at upload; existing stored .docx files stay empty. Either say so, or re-extract on first read_file miss. State which — this is a real decision, not an implementation detail.
Alternatives considered and rejected
Add mammoth / docx from npm. Rejected without measurement first: both assume Node buffers and streams. If a maintainer can show one bundling and running on workerd inside the Worker's size budget, that beats hand-rolling — but the tree's own precedent (readTar, the hand-written PDF FlateDecode path) is a 100-line reader, and unpdf was chosen for PDFs precisely because it is workerd-safe.
Send the bytes to Claude and let it read them. Rejected: resume-parse.ts:65 does this for PDFs because Claude accepts a PDF document block natively. It does not accept .docx, so this is base64 of a ZIP in a prompt.
Acceptance criteria
A fixture .docx with three paragraphs, uploaded through fileUpload, yields extracted text containing all three, in order, with paragraph breaks preserved.
A .docx containing a table yields the cell text (order may be row-major; assert the strings are present, not the layout).
A .docx whose ZIP is truncated or whose word/document.xml is missing returns status:"failed" with an error, and does not throw out of fileUpload.
A .doc returns status:"unsupported" with an error string that names the format and says it is not supported — asserted on the string, so the honesty is pinned.
After extraction, read_file on the .docx returns the text window (not the has no extracted text refusal), and search_knowledge retrieves a phrase from it.
The extraction is bounded: a .docx over the cap is refused before decompression, not after.
Regression risk
Zip-bomb shape. A small .docx can inflate enormously. The cap must apply to the decompressed size as the stream is read, not to the stored size — readTar's caller pattern in repo-ingest.ts (per-file and total caps) is the model.
extractText runs inside fileUpload, on the request path. A slow or looping reader turns every upload slow. Bound iterations over ZIP members explicitly.
Vectorisation. A newly-extractable format starts landing in Vectorize; check that the chunking path treats it exactly like PDF text and that the Index panel's per-source counts stay coherent.
The problem
A
.docxthat reaches the file store is invisible to the agent.read_filerefuses it,search_knowledgecannot retrieve from it, and the console shows a file with no content. The agent can say "there is a file called X" and nothing else about it.That is the first wall in #756: the user's emailed club form arrives,
gmail_download_attachmentstores it (once #755 is fixed), and the agent still cannot tell the user a single thing the form asks for.Priority —
P2: correctness: the platform accepts and stores the file, then reports it as unreadable. Nothing is silently wrong, but a stored document contributing nothing to RAG is a real gap in a store that advertises document-grounded chat.Where it is — VERIFIED
workers/api/src/agent-storage-utils.ts:133-149is the whole extraction dispatch:Two formats, then a catch-all. A
.docxtakes the last line.Downstream,
read_filethen fails honestly —workers/api/src/lib/storage-tools.ts:365-369:The honesty is right and should stay; the gap is that there is nothing for it to read.
Absence checked:
grep -rn "docx\|msword\|officedocument\|DecompressionStream" workers/api/src packages/sdk/src packages/browser-runner/src | grep -v '\.test\.'returns six hits — two are the MIME-type lookup table (storage-tools.ts:815-816), two areDecompressionStreamused for PDF FlateDecode (agent-storage-utils.ts:200) and repo-tarball gzip (repo-ingest.ts:205), one is a comment inresume-parse.ts:52("Claude reads PDFs natively; skip other formats (docx/txt) for now — but SAY so"), and two areaccept=".pdf,.txt,.doc,.docx"in browser-runner test fixtures. No ZIP reader exists anywhere in the tree —grep -rn "deflate-raw\|central directory\|centralDirectory" workers/api/src packages/*/srcreturns nothing..docxand.docare two different problems — do not conflate themThis is the distinction #756 collapsed, and it decides how much of this ticket is even possible.
.docx.docword/document.xml— XML with<w:t>runsThe primitives for
.docxalready exist in this runtime:DecompressionStreamis used twice today (agent-storage-utils.ts:200,repo-ingest.ts:205), andrepo-ingest.ts:245(readTar, "Minimal ustar/GNU tar reader") is the direct precedent for a hand-rolled archive walker in this codebase. A ZIP central-directory reader plus oneDecompressionStream("deflate-raw")per member getsword/document.xml; stripping<w:t>runs and inserting a newline per<w:p>gets readable text..dochas no comparable path and no dependency in this tree can be added for it that runs on workerd. The user's actual attachment in #756 is the.doc. Scope this ticket to.docxand let #756's parent carry the decision about.doc.What to do — cheapest first
extractFileText, returnstatus:"unsupported"with anerrornaming the format — a.doc/.docxshould produce "this is a Word document; the platform cannot read it yet", not silence. TheExtractedFileText.errorfield already exists (agent-storage-utils.ts:127-131). Ships in isolation and is honest either way..docxtext extraction. ZIP member reader →word/document.xml→ text. Keep it pure and next toextractPdfText, with the same size cap discipline. Once it returns text,read_file,search_knowledgeand the Index panel all light up with no further change, because they all readfileGetText..docxfiles stay empty. Either say so, or re-extract on firstread_filemiss. State which — this is a real decision, not an implementation detail.Alternatives considered and rejected
mammoth/docxfrom npm. Rejected without measurement first: both assume Node buffers and streams. If a maintainer can show one bundling and running on workerd inside the Worker's size budget, that beats hand-rolling — but the tree's own precedent (readTar, the hand-written PDF FlateDecode path) is a 100-line reader, andunpdfwas chosen for PDFs precisely because it is workerd-safe..doc, where it may be the only option — that belongs to Epic: the emailed-form workflow stops at step 2 — download is broken (#755), Word is unreadable (#763), and only the byte-upload parameter is genuinely missing (#762) #756.resume-parse.ts:65does this for PDFs because Claude accepts a PDFdocumentblock natively. It does not accept.docx, so this is base64 of a ZIP in a prompt.Acceptance criteria
.docxwith three paragraphs, uploaded throughfileUpload, yields extracted text containing all three, in order, with paragraph breaks preserved..docxcontaining a table yields the cell text (order may be row-major; assert the strings are present, not the layout)..docxwhose ZIP is truncated or whoseword/document.xmlis missing returnsstatus:"failed"with anerror, and does not throw out offileUpload..docreturnsstatus:"unsupported"with anerrorstring that names the format and says it is not supported — asserted on the string, so the honesty is pinned.read_fileon the.docxreturns the text window (not thehas no extracted textrefusal), andsearch_knowledgeretrieves a phrase from it..docxover the cap is refused before decompression, not after.Regression risk
.docxcan inflate enormously. The cap must apply to the decompressed size as the stream is read, not to the stored size —readTar's caller pattern inrepo-ingest.ts(per-file and total caps) is the model.extractTextruns insidefileUpload, on the request path. A slow or looping reader turns every upload slow. Bound iterations over ZIP members explicitly.Parent: #756. Related: #762 (writing bytes back), #755 (getting the file in).