[BUG] docx skill's merge_runs.py produces .docx files that Google Drive cannot open

Status Open
Maintainer reply None cached
Activity 1 comment · opened Sep 19, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] 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 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 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

  1. Obtain any valid .docx. A minimal one generated by python-docx or

the docx npm library is sufficient.

  1. Confirm it opens correctly (Word, LibreOffice, or Google Drive).
  1. 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.

  1. 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.

  1. Confirm the stored fileSize matches the source exactly. It does.
  1. 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.

Flagging that the invalid label was applied automatically by
github-actions, most likely because the Claude Code version field
reads "N/A - not Claude Code" while a preflight box asserting the
latest version was ticked in error. I have now unticked that box.

To restate the scope: this is not a Claude Code bug. It concerns the
public docx skill (/mnt/skills/public/docx/scripts/merge_runs.py) and
the Google Drive connector, both of which run in Anthropic's
container on claude.ai. There is no local install and no version
string to give. I filed here following the precedent of other
non-Claude-Code issues on this repository.

The core finding stands and is reproducible in one step: running
merge_runs.py over a valid .docx — where it reports "Merged 0 runs"
and changes no document content — produces a file that Google Drive
cannot open. A single-variable test is in the body: 9,292 bytes
opens, the same file at 9,275 bytes after the helper does not.

If this repository is the wrong venue for skill and connector
defects, I would be grateful to be pointed at the right one rather
than have the report closed.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗