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
{{ message }}
Repository navigation
fix(server): Word text boxes are no longer extracted twice for agents - #29827
When a user attaches a Word document that contains a text box (callouts, sidebars, labelled diagrams), the agent sees every text-box line twice, run together: Deadline moved to FridayDeadline moved to Friday.
Word saves each text box inside mc:AlternateContent. The text appears once in mc:Choice (the DrawingML shape) and again in mc:Fallback (a VML copy for older readers). The document text extractor walks the whole XML tree, so it reads both copies.
Change
The XML parser in documentTextWorker.ts now drops a Fallback element when it closes, but only if a sibling Choice in the same AlternateContent already holds text. Under Markup Compatibility (ECMA-376 Part 3), a Fallback is an alternative to its Choice branches. When the Choice carries the text, as with Word text boxes, the Fallback only duplicates it. When no Choice has text, for example a chart Choice with a VML caption in the Fallback, the Fallback is kept, so text that exists only there still reaches the agent, as it does on main. The parser records, as each tag closes, whether a node holds text and whether a text-bearing Choice has closed under it, so the check costs the same at every Fallback. A document with 100,000 sibling Fallbacks extracts in 0.13s. Doing this at parse time covers text(), paragraphs(), hasCellText() and all() in one place, so none of the hidden-element lists change.
Scope and approval
Very small, focused fix for an obvious bug, so no prior issue: Word writes this markup for every text box, and the duplicated text reaches the agent's prompt. One non-test line, plus one row added to the existing extraction table test.
Overlap search: the local PR index and a GitHub search for "text box" docx turned up only #26230 and #29051 (web Office preview CJK fonts), which do not touch this code. Open PR #29796 edits the hidden-element lists in the same file. This change stays out of those lines and the two do not conflict.
Verification
Red on main: the new docx row in userInputAttachments.test.ts ("preserves office structure and EPUB reading order with bounded archives") fails with AssertionError: expected 2 to be 1, because the text-box text occurs twice.
Fallback-only text: a second docx row has a chart Choice with no text and a VML caption in its Fallback. Dropping every Fallback failed it with Error: No text extracted (on 18d9b24). It passes now.
Green: vp test run src/provider/userInputAttachments.test.ts passes, 30/30.
vp run check:changed: format, lint, knip, typecheck and tests all pass.
Runtime: I built a docx with Word's real namespaces and text-box markup (wps:txbx in Choice, v:textbox in Fallback) and ran the extractor through node src/bin.ts __document-text. Main printed Project update\nDeadline moved to FridayDeadline moved to Friday. This branch prints Project update\nDeadline moved to Friday.
maria-rcks verified the duplicate fix with a real Word-saved file (shapes-with-text.docx from Apache POI): main gave two occurrences of each label, this PR gives one. No real fallback-only document was found, so that case is covered by the fixture only.
Reviewed with GPT-6.1 Sol on high, 1 round per commit; all approved. The fallback round also probed nested, whitespace-only and tab/br-only Choices, and confirmed ODF and XLSX are unchanged.
Created with Claude Opus 5.5 in Claude Code (T3 Code).
The XML parser tracks non-whitespace text in nodes and choices. When a fallback closes under a parent with a text-bearing choice, the parser removes the fallback. DOCX tests cover supported and unsupported choices.
Changes: DOCX Fallback Text Handling
Layer / File(s)
Summary
Fallback handling and DOCX extraction tests apps/server/src/provider/documentTextWorker.ts, apps/server/src/provider/userInputAttachments.test.ts
The parser tracks non-whitespace text and removes fallback content when its parent has a text-bearing choice. DOCX tests cover shared callout text in a supported choice and fallback, plus chart-caption text in a fallback when the choice requires unsupported cx.
This change stops Word text boxes from being extracted twice and still keeps text that appears only in a fallback. No concrete regression remains. An unusual grouped-drawing shape is unconfirmed and does not block merging.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title clearly and concisely describes the primary change: preventing duplicate extraction of Word text-box content for agents.
Description check
The description includes all required sections and provides clear problem context, implementation details, scope justification, and specific verification results.
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Finishing Touches
Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Warning
Some tools did not complete. Review the errors below.
ast-grep (0.51.1)
ast-grep could not parse rule config: /ast-grep-rules/ast-grep-essentials/rules/rust/security/postgres-empty-password-rust.yml
CodeRabbit Verify
Run CodeRabbit Verify to test the changes in this pull request.
Macroscope's review found this PR approvable — The extractor removes duplicate Word text-box fallback content while retaining fallback-only text. The change is confined to derived text extraction, with existing resource limits and source documents unaffected.
You can add or adjust custom eligibility rules. Learn more.
verified the duplicate-text fix at 18d9b24748b5e2758b9f672d6f268ddf81152817 using repo Playwright, /usr/bin/google-chrome, the running web composer, attachment upload/extraction, and real Codex gpt-6.1-sol turns. projects and threads were created through the app. no mocks, seeded conversations, or source changes.
on github/main (61c3794c32), attaching the unchanged Word-saved shapes-with-text.docx made codex report two occurrences of each of five labels. on this exact pr head, the same file and prompt produced one occurrence each. floating text boxes, square/ellipse text, and grouped-shape text all survived. the uploaded bytes matched the original sha256 on both servers. the screenshot branch labels describe the project checkout; the app servers ran main on port 8693 and the pr on port 5733, sequentially.
before, main:
after, pr:
neighboring behavior: attaching the unchanged Bug66263-table.docx through the same client/provider path preserved SDT Cell 1\tSDT Cell 2 in the sidecar and codex reply.
fallback-only content remains a correctness concern from the existing discussion: the parser removes every Fallback, even when its Choice has no readable text. no authentic fallback-only document was found in the inspected document collection, so this case was not exercised in the app and the evidence above does not establish that it is safe.
native desktop, ios/android, macos/windows, other providers, and other document formats were not exercised on this linux vm. this verifies the duplicate-text scenario and the listed neighboring behaviors, not those surfaces.
Delta since your verification at 18d9b24: e4039d2 addresses the fallback-only concern you and CodeRabbit raised. A Fallback is now dropped only when a sibling Choice already has text, so fallback-only text, such as a chart with a VML caption, is kept as on main. The duplicate case you verified with shapes-with-text.docx takes the same path, because its Choice carries the text. The new fixture row fails on 18d9b24 (No text extracted) and passes now. Sol also checked that ODF and XLSX extraction is unchanged.
56-56: Major · Functional Correctness · Heavy lift
Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.
Preserve fallback-only text in grouped drawings.
A DOCX group can contain both text boxes and charts. (help.syncfusion.com) If the Choice yields text from a text box but the chart caption exists only in the Fallback, hasTextChoice causes the entire Fallback to be removed. The extracted document then loses the caption.
Remove only fallback content known to be redundant, or handle unsupported grouped objects before discarding the Fallback.
Reviewing files that changed from the base of the PR and between e4039d2 and 7ce5f6c.
Files selected for processing: 1
apps/server/src/provider/documentTextWorker.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.
On the review's unverified note about grouped drawings (documentTextWorker.ts:56): leaving this as is. The rule works per AlternateContent. If its Choice carries text, the Fallback is treated as a copy of that same object. Word writes a chart's VML fallback as a picture, not text, and chart captions are normally paragraphs outside the drawing. So I haven't found a document where a group's Fallback carries text its Choice lacks. maria-rcks's real Word file (shapes-with-text.docx, with grouped shapes) extracted each label once with nothing lost. Removing exactly the redundant parts of a group would mean pairing DrawingML elements with their VML counterparts, which is the heavy lift you mention, with no failing document to justify it. If such a file turns up, it would make a good fixture for a follow-up.
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
size:S10-29 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.
2 participants
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.
Problem
When a user attaches a Word document that contains a text box (callouts, sidebars, labelled diagrams), the agent sees every text-box line twice, run together:
Deadline moved to FridayDeadline moved to Friday.Word saves each text box inside
mc:AlternateContent. The text appears once inmc:Choice(the DrawingML shape) and again inmc:Fallback(a VML copy for older readers). The document text extractor walks the whole XML tree, so it reads both copies.Change
The XML parser in
documentTextWorker.tsnow drops aFallbackelement when it closes, but only if a siblingChoicein the sameAlternateContentalready holds text. Under Markup Compatibility (ECMA-376 Part 3), a Fallback is an alternative to its Choice branches. When the Choice carries the text, as with Word text boxes, the Fallback only duplicates it. When no Choice has text, for example a chart Choice with a VML caption in the Fallback, the Fallback is kept, so text that exists only there still reaches the agent, as it does onmain. The parser records, as each tag closes, whether a node holds text and whether a text-bearing Choice has closed under it, so the check costs the same at every Fallback. A document with 100,000 sibling Fallbacks extracts in 0.13s. Doing this at parse time coverstext(),paragraphs(),hasCellText()andall()in one place, so none of the hidden-element lists change.Scope and approval
Very small, focused fix for an obvious bug, so no prior issue: Word writes this markup for every text box, and the duplicated text reaches the agent's prompt. One non-test line, plus one row added to the existing extraction table test.
Overlap search: the local PR index and a GitHub search for "text box" docx turned up only #26230 and #29051 (web Office preview CJK fonts), which do not touch this code. Open PR #29796 edits the hidden-element lists in the same file. This change stays out of those lines and the two do not conflict.
Verification
userInputAttachments.test.ts("preserves office structure and EPUB reading order with bounded archives") fails withAssertionError: expected 2 to be 1, because the text-box text occurs twice.Choicewith no text and a VML caption in itsFallback. Dropping every Fallback failed it withError: No text extracted(on 18d9b24). It passes now.vp test run src/provider/userInputAttachments.test.tspasses, 30/30.vp run check:changed: format, lint, knip, typecheck and tests all pass.wps:txbxin Choice,v:textboxin Fallback) and ran the extractor throughnode src/bin.ts __document-text. Main printedProject update\nDeadline moved to FridayDeadline moved to Friday. This branch printsProject update\nDeadline moved to Friday.shapes-with-text.docxfrom Apache POI): main gave two occurrences of each label, this PR gives one. No real fallback-only document was found, so that case is covered by the fixture only.Reviewed with GPT-6.1 Sol on high, 1 round per commit; all approved. The fallback round also probed nested, whitespace-only and tab/br-only Choices, and confirmed ODF and XLSX are unchanged.
Created with Claude Opus 5.5 in Claude Code (T3 Code).
🤖 Generated with Claude Code