fix(docs): drop tabs field mask to support smart-chip docs in get/write/replace#382
Conversation
When a Google Doc contains smart chips (Person, Date, RichLink, Dropdown, or other structural inline tokens), calls to `docs.documents.get` with `fields: 'tabs'` and `includeTabsContent: true` fail with: Field mask cannot retrieve comment-specific fields when include_comments is false. The Docs API treats some content reachable via the `tabs` mask as comment-anchored when smart chips are present, but the REST API does not expose any `include_comments` parameter that callers can set. Removing the explicit field mask while keeping `includeTabsContent: true` sidesteps the trigger and works on both smart-chip and plain docs. Fixed in three call sites in `DocsService.ts`: - `writeText` — end-of-doc preflight (resolving `position: 'end'`) - `getText` - `replaceText` — preflight to locate ranges Repro: any Google Doc containing at least one smart chip, calling `docs_getText`, `docs_writeText`, or `docs_replaceText` against it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request removes the explicit 'fields: 'tabs'' mask from Google Docs API calls in DocsService.ts to prevent errors when fetching documents containing smart chips. While this successfully avoids API errors, the reviewer identified a critical index mismatch and potential data corruption bug in replaceText when dealing with smart chips. Because smart chips are rendered as markdown strings in _getFullDocumentText, the text length exceeds the actual document's index space, causing index drift and misaligned replacement requests. The reviewer recommends introducing a keepAlignment flag to represent smart chips with length-aligned placeholders.
| const docBefore = await docs.documents.get({ | ||
| documentId: id, | ||
| fields: 'tabs', | ||
| includeTabsContent: true, | ||
| }); |
There was a problem hiding this comment.
Critical Index Mismatch / Data Corruption Bug in replaceText with Smart Chips
While this PR successfully fixes the API error when fetching documents containing smart chips, enabling replaceText on these documents exposes a severe correctness and potential data corruption bug.
Root Cause
replaceTextcalls_generateReplacementRequests, which relies on_getFullDocumentTextto build a plain-text representation of the document for finding search term occurrences._getFullDocumentTextcalls_readStructuralElement, which renders smart chips (Person, RichLink, Date) into markdown strings (e.g.,[John Doe](mailto:john@example.com)).- In a Google Doc, a smart chip occupies a specific index span (typically exactly 1 character in the document's UTF-16 coordinate space).
- By rendering the chip as a long markdown string, the length of
documentTextbecomes much larger than the actual document's index space. This causes all subsequent text indices to drift and become completely misaligned. _generateReplacementRequeststhen uses these drifted indices to constructdeleteContentRangeandinsertTextrequests, which will point to incorrect offsets in the actual document.
Consequence
This will result in either:
- API Errors: The update requests fail because the calculated indices are out of bounds.
- Silent Data Corruption: The API successfully executes the update but deletes/replaces the wrong text in the document, corrupting user data.
Recommended Solution
Introduce a parameter to _readStructuralElement and _getFullDocumentText to return index-aligned text (where smart chips are represented by placeholders of their actual document length) instead of their rendered markdown:
- Update
_readStructuralElementto accept akeepAlignment?: booleanflag. - If
keepAlignmentistrue, instead of rendering the markdown for person/link/date chips, return a placeholder string (e.g., spaces or\uFFFC) of length(pElement.endIndex ?? 0) - (pElement.startIndex ?? 0)(which is typically1). - Pass
keepAlignment: truewhen calling_getFullDocumentTextfrom_generateReplacementRequests.
Summary
docs_getText,docs_writeText, anddocs_replaceTextall fail against any Google Doc containing smart chips (Person, Date, RichLink, Dropdown, etc.) with:The Docs API treats some content reachable via the
fields: 'tabs'mask as comment-anchored once smart chips are present in the doc. The REST API does not expose anyinclude_commentsparameter that callers can set — an attempt to passincludeComments: trueis rejected by the SDK asUnknown name 'include_comments'. The fix is to drop the explicit field mask while keepingincludeTabsContent: true, which still returns the tab content but no longer trips the trigger.What changed
workspace-server/src/services/DocsService.ts, three call sites:writeText— end-of-doc preflight (resolvingposition: 'end')getTextreplaceText— preflight that locates ranges before issuingreplaceAllTextrequestsEach
docs.documents.get(...)call now passes onlydocumentIdandincludeTabsContent: true. The downstream code only consumestabs.tabProperties.tabIdandtabs.documentTab.body.content(via the existing_flattenTabshelper), all of which are returned in the API default field set.Repro
Any Google Doc with at least one smart chip and any of the three affected tools. Before this PR: field-mask error. After: full body returned, smart chips preserved as inline tokens.
Test plan
npm run buildcleandocs_getTextagainst a doc with Person, Date, and RichLink chips → returns body🤖 Generated with Claude Code