Skip to content

fix: detect and decode UTF-16 BOM in read_file (#610) - #621

Open
PiedPiper911 wants to merge 1 commit into
wonderwhy-er:mainfrom
PiedPiper911:fix/utf16-le-file-decoding-610
Open

fix: detect and decode UTF-16 BOM in read_file (#610)#621
PiedPiper911 wants to merge 1 commit into
wonderwhy-er:mainfrom
PiedPiper911:fix/utf16-le-file-decoding-610

Conversation

@PiedPiper911

@PiedPiper911 PiedPiper911 commented Aug 4, 2026

Copy link
Copy Markdown

Problem

When PowerShell 5.1 writes files via redirection (echo "hello" > file.txt), it uses UTF-16 LE encoding. The read_file tool reads these files as UTF-8, producing text with embedded NUL characters that breaks downstream processing.

Solution

Detect UTF-16 BOM (Byte Order Mark) at the start of file content and decode accordingly:

  • 0xFF 0xFE → UTF-16 LE
  • 0xFE 0xFF → UTF-16 BE
  • Otherwise → UTF-8 (existing behavior)

Changes

  • src/tools/filesystem.ts: Added BOM detection and UTF-16 decoding in the file reading path

Testing

  • Files without BOM continue to work as before (UTF-8)
  • UTF-16 LE files from PowerShell are correctly decoded
  • UTF-16 BE files are also handled

Fixes #610

Summary by CodeRabbit

  • Bug Fixes
    • Improved support for reading UTF-16 encoded files.
    • Automatically detects byte order, removes encoding markers, and preserves line-based content selection.
    • Maintains existing handling for UTF-8 and other file formats.

PowerShell 5.1 on Windows writes files as UTF-16 LE when using
redirection (e.g., echo "hello" > file.txt). The read_file tool
previously read these as UTF-8, producing garbled text with embedded
NUL characters.

Added BOM detection (0xFF 0xFE for UTF-16 LE, 0xFE 0xFF for UTF-16 BE)
in readFileFromDisk and readFileInternal. When a BOM is detected, the
file is decoded using TextDecoder with the correct encoding and the BOM
character is stripped from the output.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The filesystem now detects UTF-16 little-endian and big-endian BOMs for disk and internal reads. It decodes matching files, removes BOMs, preserves line-based selection, and falls back to existing handling when detection or decoding fails.

Changes

UTF-16 read support

Layer / File(s) Summary
UTF-16 decoding across read paths
src/tools/filesystem.ts
Adds UTF-16 BOM detection and decoding for disk and internal reads. Disk reads preserve offset and length selection. Non-UTF-16 files use existing handling or UTF-8 decoding.

Estimated code review effort: 2 (Simple) | ~15 minutes

Suggested reviewers: wonderwhy-er, edgarsskore

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: detecting and decoding UTF-16 BOMs in read_file.
Linked Issues check ✅ Passed The changes satisfy issue [#610] by detecting UTF-16 BOMs and decoding UTF-16 LE files without embedded NUL characters.
Out of Scope Changes check ✅ Passed The UTF-16 BE support, BOM removal, and internal-read handling are related extensions of the requested UTF-16 decoding behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/tools/filesystem.ts`:
- Around line 525-526: Update the BOM-detection flow around
fs.readFile(validPath) to read only the first two bytes using
runWithAbortableTimeout with the existing three-minute cancellation behavior.
Only perform a full-file read when the BOM confirms UTF-16, and ensure that read
also runs through runWithAbortableTimeout; preserve the normal handler path
without rereading non-UTF-16 files.
- Around line 529-530: Update both TextDecoder constructions in
src/tools/filesystem.ts at lines 529-530 and 671-672 to use fatal UTF-16
decoding, so malformed input throws and is handled explicitly before internal
edit operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 508ce956-17a5-4005-b9e3-eeff4d38bd84

📥 Commits

Reviewing files that changed from the base of the PR and between 9bd8422 and 4d93e21.

📒 Files selected for processing (1)
  • src/tools/filesystem.ts

Comment thread src/tools/filesystem.ts
Comment on lines +525 to +526
try {
const rawBuffer = await fs.readFile(validPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep BOM probing bounded and cancellable.

fs.readFile(validPath) at Line [526] reads the entire file before the existing timeout starts. It does this for every file without a BOM, and the normal handler reads non-UTF-16 files again. A large or stalled file can consume excessive memory and keep the request pending past the three-minute timeout, even when the caller requests a small line range.

Read only the first two bytes under runWithAbortableTimeout. Read the full file only after a BOM is confirmed, and keep that full read cancellable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/filesystem.ts` around lines 525 - 526, Update the BOM-detection
flow around fs.readFile(validPath) to read only the first two bytes using
runWithAbortableTimeout with the existing three-minute cancellation behavior.
Only perform a full-file read when the BOM confirms UTF-16, and ensure that read
also runs through runWithAbortableTimeout; preserve the normal handler path
without rereading non-UTF-16 files.

Comment thread src/tools/filesystem.ts
Comment on lines +529 to +530
const decoder = new TextDecoder(utf16Encoding);
let decoded = decoder.decode(rawBuffer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline src/tools/filesystem.ts --match TextDecoder --view expanded || true

echo "== relevant lines =="
sed -n '500,545p' src/tools/filesystem.ts
printf '\n---\n'
sed -n '650,685p' src/tools/filesystem.ts

echo "== TextDecoder construction sites =="
rg -n "new TextDecoder|decoder.decode|utf16Encoding|replace\(" src/tools/filesystem.ts

echo "== deterministic JS probe for default TextDecoder behavior =="
node - <<'JS'
for (const inputHex of [
  'd800',
  'dfff',
  'fffd',
  'd8000061',
]) {
  const buffer = Uint8Array.from(inputHex.split(/(..)/).filter(Boolean).map(h => parseInt(h, 16)));
  console.log(inputHex, new TextDecoder().decode(buffer));
}
JS

Repository: wonderwhy-er/DesktopCommanderMCP

Length of output: 4371


Use fatal UTF-16 decoders when decoding errors must be handled explicitly.

Both decoder calls use replacement mode by default, so malformed UTF-16 decoding produces U+FFFD instead of failing and being handled explicitly. Use { fatal: true } at these sites, or reject malformed content before internal edit operations.

  • src/tools/filesystem.ts#L529-L530
  • src/tools/filesystem.ts#L671-L672
📍 Affects 1 file
  • src/tools/filesystem.ts#L529-L530 (this comment)
  • src/tools/filesystem.ts#L671-L672
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/filesystem.ts` around lines 529 - 530, Update both TextDecoder
constructions in src/tools/filesystem.ts at lines 529-530 and 671-672 to use
fatal UTF-16 decoding, so malformed input throws and is handled explicitly
before internal edit operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

read_file does not decode UTF-16 LE files created by Windows PowerShell 5.1

1 participant