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
The --dry-run flag is silently ignored by gog docs find-replace. All four invocation variants execute the mutation against the live document. This is a data-loss-class bug — the empty-replacement variant has corrupted production documents in the past.
Severity
Data-loss class. The empty-replacement + --first + --dry-run combination can leave documents with matched text deleted but not replaced (because the underlying implementation is a non-atomic delete-then-insert pair). I lost content from a signed Letter of Engagement to this exact pattern on 2026-03-25.
Steps to Reproduce
Tested on v0.11.0-33-gd3ef84d (current feat/docs-replace-cmd HEAD).
DOC=<a fresh doc with the strings used below># Variant 1 — positional replacement + --dry-run
gog docs find-replace $DOC"old text""new text" --dry-run --first
gog docs cat $DOC| grep "new text"# MUTATED# Variant 2 — --content-file + --dry-runecho"PAYLOAD"> /tmp/p.txt
gog docs find-replace $DOC"anchor" --content-file /tmp/p.txt --dry-run --first
gog docs cat $DOC| grep "PAYLOAD"# MUTATED# Variant 3 — --content-file + --format markdown + --dry-runecho"**bold**"> /tmp/p.md
gog docs find-replace $DOC"anchor" --content-file /tmp/p.md --format markdown --dry-run --first
gog docs cat $DOC| grep "bold"# MUTATED# Variant 4 — empty replacement + --dry-run + --first (DATA LOSS)
gog docs find-replace $DOC"anchor""" --dry-run --first
# Output reports: "Google API error (400 badRequest): Insert text requests must specify text to insert"
gog docs cat $DOC| grep "anchor"# GONE — anchor text was deleted before the insert failed
Output of Variant 1 (positional + --dry-run)
documentId <docId>
find old text
replacements 1
No "would replace" preview language, no dry-run indicator. The replacements: 1 line is the same line the non-dry-run path emits when it actually mutates.
Expected Behavior
--dry-run should print what would happen and exit 0 without making any API mutation calls.
For --first flows specifically, the implementation should be transactional or at least guard against partial-application data loss when a downstream insert request fails.
Actual Behavior
--dry-run is silently ignored. The mutation happens.
For --first with empty replace, the delete-then-insert pair is non-atomic: the delete succeeds, the insert fails (Insert text requests must specify text to insert), and the matched text is lost with no recovery path.
Honour --dry-run end-to-end. In find-replace, gate every code path that calls a Google Docs mutation API on !cfg.DryRun. Print the planned operations (occurrences found, requests that would be sent) and return.
Make the --first path atomic. Bundle the deleteContentRange and insertText requests into a single documents.batchUpdate call so they apply atomically, or guard against the empty-insertText case before the delete fires. Currently the user can lose data by passing an empty replacement.
Validate replace content is non-empty before any API call when the implementation requires a non-empty insert. The current "replace with a space and clean up later" workaround documented in user notes wouldn't be needed if the CLI rejected the empty case at parse time.
The CLAUDE.md / user-notes warning ("DANGER: gog docs find-replace --dry-run is broken") was written after the 2026-03-25 LoE incident. The bug was never fixed; today's testing confirms it is still live in v0.11.0-33.
Workaround
Until fixed: never pass --dry-run to gog docs find-replace. Test on a copy of the document instead. The /gws skill in my Claude Code setup explicitly forbids the --dry-run --content-file combination on this command.
Summary
The
--dry-runflag is silently ignored bygog docs find-replace. All four invocation variants execute the mutation against the live document. This is a data-loss-class bug — the empty-replacement variant has corrupted production documents in the past.Severity
Data-loss class. The empty-replacement +
--first+--dry-runcombination can leave documents with matched text deleted but not replaced (because the underlying implementation is a non-atomic delete-then-insert pair). I lost content from a signed Letter of Engagement to this exact pattern on 2026-03-25.Steps to Reproduce
Tested on
v0.11.0-33-gd3ef84d(currentfeat/docs-replace-cmdHEAD).Output of Variant 1 (positional + --dry-run)
No "would replace" preview language, no dry-run indicator. The
replacements: 1line is the same line the non-dry-run path emits when it actually mutates.Expected Behavior
--dry-runshould print what would happen and exit 0 without making any API mutation calls.--firstflows specifically, the implementation should be transactional or at least guard against partial-application data loss when a downstream insert request fails.Actual Behavior
--dry-runis silently ignored. The mutation happens.--firstwith empty replace, the delete-then-insert pair is non-atomic: the delete succeeds, the insert fails (Insert text requests must specify text to insert), and the matched text is lost with no recovery path.Environment
gog --version:v0.11.0-33-gd3ef84d (d3ef84deb6ca 2026-05-01T13:38:07Z)feat/docs-replace-cmdSuggested Fix
--dry-runend-to-end. Infind-replace, gate every code path that calls a Google Docs mutation API on!cfg.DryRun. Print the planned operations (occurrences found, requests that would be sent) and return.--firstpath atomic. Bundle thedeleteContentRangeandinsertTextrequests into a singledocuments.batchUpdatecall so they apply atomically, or guard against the empty-insertText case before the delete fires. Currently the user can lose data by passing an empty replacement.replacecontent is non-empty before any API call when the implementation requires a non-empty insert. The current "replace with a space and clean up later" workaround documented in user notes wouldn't be needed if the CLI rejected the empty case at parse time.Related
find-replace --format markdown(no replacement at all). Not the same root cause.gog docs find-replace --dry-runis broken") was written after the 2026-03-25 LoE incident. The bug was never fixed; today's testing confirms it is still live in v0.11.0-33.Workaround
Until fixed: never pass
--dry-runtogog docs find-replace. Test on a copy of the document instead. The/gwsskill in my Claude Code setup explicitly forbids the--dry-run --content-filecombination on this command.