Skip to content

bugFix(tools): refuse edits to PDF and DOCX documents - #1125

Open
addyCooks wants to merge 1 commit into
Nano-Collective:mainfrom
addyCooks:fix/document-edit-overwrite
Open

bugFix(tools): refuse edits to PDF and DOCX documents#1125
addyCooks wants to merge 1 commit into
Nano-Collective:mainfrom
addyCooks:fix/document-edit-overwrite

Conversation

@addyCooks

Copy link
Copy Markdown
Contributor

Closes #1058

Description

getCachedFileContent is the single read path for the edit tools, and for .pdf
and .docx it returns a markdown transcript instead of the file's bytes. The write
side had no matching branch, and CachedFile carried nothing that let a caller tell
derived content from real content so no caller could check.

Changes:

  • isDerivedContentPath() in file-cache.ts is now the single source of truth for
    which formats are transcribed. The conversion branch uses it too, so the read and
    write sides cannot drift apart as formats are added. A duplicate .pdf/.docx
    check in read-file.tsx was folded into it.
  • CachedFile gained a derived flag, closing the gap the issue named.
  • validateEditableFormat() in path-validators.ts is the shared guard, wired into
    the validator and the executor of all three write tools. The executor copy is
    not redundant: ToolRegistry.fromToolExports registers the raw tool alongside
    the withValidation-wrapped handler, so execute is reachable without the
    validator.
  • The check is on the extension rather than on cached content, so it also covers a
    fresh write_file to a .pdf that was never read writing UTF-8 text to those
    paths is never correct.
  • The guard fires before getCachedFileContent, so a refused edit never pays for a
    document conversion.

Scope note: #962 covers the same class of loss in checkpoints (UTF-8 round-tripping
destroying binaries on save). This PR is the edit-tool path only; the two fixes are
independent.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • If this was for an open issue, I was assigned to it
  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging

Reading a .pdf or .docx returns a markdown transcript rather than the
file's bytes, and the write path had no matching branch: string_replace,
diff_edit and write_file applied the edit to that transcript and wrote it
back over the document as UTF-8. A request to fix one word replaced a
real document with a few hundred bytes of plain text, and the tool
reported success. Neither undo system could recover it -- checkpoints
skip binaries and file snapshots store text -- so the original bytes were
gone the moment the write landed.

The three write tools now refuse any path whose content the read path can
only transcribe, naming the reason so the model stops instead of
retrying. The check is on the extension, so it also covers a fresh
write_file to a .pdf that was never read. CachedFile additionally carries
a derived flag, so a caller holding cached content can tell whether it is
the file or a transcript of it.

Closes Nano-Collective#1058
@github-actions github-actions Bot added the area:tools Tool implementations and tool-calling label Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tools Tool implementations and tool-calling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Editing a PDF or DOCX overwrites the document with its markdown transcript

1 participant