anthropics / anthropics/claude-code
[BUG] docx skill's merge_runs.py produces .docx files that Google Drive cannot open
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 147k
- Forks
- 24k
- PR merge metrics
- PR metrics pending
Description
Preflight Checklist
- I have searched existing issues and this hasn't been reported yet
- This is a single bug report (please file separate reports for different bugs)
- I am using the latest version of Claude Code
What's Wrong?
Issue body
Scope note: this concerns the public docx skill and the Google Drive connector on claude.ai, not Claude Code itself. Filing here follows the precedent of other non-Claude-Code issues on this repo (#92191, #90359, #78368, #95115).
Summary
/mnt/skills/public/docx/scripts/merge_runs.py re-serialises word/document.xml through lxml. The resulting .docx opens correctly in Word and LibreOffice but cannot be opened in Google Drive after being written there via the Drive connector. The helper causes this even when it modifies no content at all — it reports Merged 0 runs and still renders the file unopenable.
Any Claude session that follows the docx skill's guidance and then writes the result to Drive produces a silently corrupt file.
Reproduction
1. Create or obtain a valid .docx.
2. Unzip it; run python3 /mnt/skills/public/docx/scripts/merge_runs.py <unpacked_dir>/; rezip.
3. Write the result to Google Drive via the connector's create_file with base64Content.
4. Open it in Google Drive.
Expected: the file opens.
Actual: "Can't open this file right now. Please try again later", persistently. read_file_content on the stored file returns an empty string. The file still renders correctly in LibreOffice locally.
Single-variable evidence
A 9,292-byte .docx written to Drive opened correctly. The same file with only merge_runs.py applied — no content change — became 9,275 bytes and would not open.
Eight files tested, all rendered locally to PDF before upload, all stored sizes verified against source, all open-results confirmed by a human:
| Package built by | lxml re-serialised | Size | Opens in Drive |
|---|---|---|---|
| docx (npm) | No | 9,292 | Yes |
| zip repack of edited doc | Yes | 14,670 | No |
| zip repack of edited doc | Yes | 14,670 | No |
| python-docx re-save of the above | Yes (inherited) | 13,324 | No |
| docx (npm) | No | 17,316 | Yes |
| zip repack of generated doc | No | 9,292 | Yes |
| original untouched file | No | 14,085 | Yes |
| the 9,292 passing file + merge_runs.py | Yes | 9,275 | No |
The lxml column partitions the results exactly. Packaging method does not (row 6 passes with the same zip -Xr as rows 2–3). File size does not (row 5 at 17,316 passes while rows 2–3 at 14,670 fail).
Probable mechanism
The visible effect is that the XML declaration changes from to the same without standalone, which OOXML requires. Not isolated conclusively, but it is the only declaration-level difference and Google's parser is stricter than Word's or LibreOffice's.
Impact
A governance document was destroyed mid-migration in our case. It was recoverable only because the original was still in Drive Trash. The failure is silent at every automated checkpoint — stored size matches the source exactly, download_file_content returns matching bytes, no error is raised. It is detectable only by a human opening the file.
Suggested fix
Preserve the XML declaration (including standalone="yes") when re-serialising, or avoid re-serialising the whole document when no runs are merged.
What Should Happen?
The .docx should open in Google Drive.
merge_runs.py should preserve the XML declaration, including
standalone="yes", when it re-serialises word/document.xml. It should
also not re-serialise the document at all when it merges zero runs,
as in this case.
Error Messages/Logs
Google Drive, on opening the stored file:
Can't open this file right now. Please try again later.
(Persistent, not transient. Reproduced on a deliberate retry several
minutes later.)
Google Drive connector, read_file_content on the same stored file:
{"fileContent": ""}
No error is raised anywhere in the write path. create_file returns
success, the stored fileSize matches the source byte for byte, and
download_file_content returns base64 consistent with what was sent.
XML declaration before merge_runs.py:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
After merge_runs.py (script output: "Merged 0 runs"):
<?xml version="1.0" encoding="UTF-8"?>
Steps to Reproduce
-
Obtain any valid .docx. A minimal one generated by python-docx or
the docx npm library is sufficient. -
Confirm it opens correctly (Word, LibreOffice, or Google Drive).
-
Unzip it, run the docx skill's helper over the unpacked directory,
and rezip:unzip -q original.docx -d unpacked/ python3 /mnt/skills/public/docx/scripts/merge_runs.py unpacked/ cd unpacked && zip -Xrq ../after.docx .The script prints "Merged 0 runs" — it changes no document content.
-
Write after.docx to Google Drive via the Drive connector's
create_file, using the base64Content parameter with
contentMimeType application/vnd.openxmlformats-officedocument.
wordprocessingml.document and disableConversionToGoogleType true. -
Confirm the stored fileSize matches the source exactly. It does.
-
Open the stored file in Google Drive.
Result: step 6 fails with "Can't open this file right now."
original.docx, written to Drive by the identical route in step 4,
opens without issue. The only difference between the two files is
step 3.
Tested at 9,292 bytes (original, opens) versus 9,275 bytes (after
merge_runs.py, does not open). Also reproduced at 14,670 and 13,324
bytes. Files that never passed through the helper opened at 9,292,
14,085 and 17,316 bytes, so this is not a size effect. after.docx
still renders correctly in LibreOffice locally.
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
No response
Claude Code Version
N/A - not Claude Code (claude.ai, Opus)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
Scope: this concerns the public docx skill and the Google Drive
connector on claude.ai, not Claude Code. The Claude Code version,
platform, OS and terminal fields above are required by the template
but do not apply — no terminal or local install is involved. The
helper and the connector both run in Anthropic's container.
Searched existing issues for "merge_runs docx" before filing — no
results.
Full test matrix. Every file was rendered to PDF locally before
upload and confirmed correct; every stored size was verified against
source; every open-result was confirmed by a human opening the file
from Drive.
| Package built by | lxml re-serialised | Size | Opens in Drive |
|---|---|---|---|
| docx (npm) | No | 9,292 | Yes |
| zip repack of edited doc | Yes | 14,670 | No |
| zip repack of edited doc | Yes | 14,670 | No |
| python-docx re-save of the above | Yes (inherited) | 13,324 | No |
| docx (npm) | No | 17,316 | Yes |
| zip repack of generated doc | No | 9,292 | Yes |
| original untouched file | No | 14,085 | Yes |
| the 9,292 passing file + merge_runs.py | Yes | 9,275 | No |
The lxml column partitions the results exactly. Packaging method does
not — row 6 passes using the same zip -Xr as rows 2 and 3. File size
does not — row 5 at 17,316 bytes passes while rows 2 and 3 at 14,670
fail, so the relationship is not monotonic and no size ceiling exists
between those values.
Note that row 4 fails because python-docx faithfully preserved an
already-damaged document. Re-saving through a library does not repair
a file once merge_runs.py has touched it.
Impact: this destroyed a governance document mid-migration. It was
recoverable only because the original was still in Drive Trash. An
agent has no way to detect the failure — create_file reports success,
stored fileSize matches source exactly, and download_file_content
returns matching bytes.
Separately, the Drive connector's create_file accepts binary content
as an inline base64 string with no integrity check in either
direction. During this investigation three payloads were transmitted
truncated or malformed; two were caught only by manually comparing
stored fileSize against source. A content-hash parameter, or a
checksum in the response, would catch both that and the corruption
above. Filing separately as it is a distinct defect.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading /mnt/skills/public/docx/scripts/merge_runs.py and inspect how it serializes word/document.xml when it reports “Merged 0 runs.” Reproduce the before-and-after XML declaration and verify the resulting .docx through the Drive connector’s create_file path; done means untouched documents retain standalone="yes", zero-run inputs avoid unnecessary re-serialization, and the file opens in Google Drive.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100