Skip to content

fix: release file handles after truncated reads - #571

Open
Soumyajit2288 wants to merge 2 commits into
wonderwhy-er:mainfrom
Soumyajit2288:agent/release-truncated-read-handles
Open

fix: release file handles after truncated reads#571
Soumyajit2288 wants to merge 2 commits into
wonderwhy-er:mainfrom
Soumyajit2288:agent/release-truncated-read-handles

Conversation

@Soumyajit2288

@Soumyajit2288 Soumyajit2288 commented Jul 11, 2026

Copy link
Copy Markdown

Summary

  • explicitly close and destroy readline-backed file streams when a bounded read stops before EOF
  • wait for the underlying stream's close event before returning
  • apply the same cleanup to the large-file sampling and estimated-position paths
  • add a regression test that immediately replaces a file after a truncated read

Root cause

The bounded text-read paths could break out of for await before reaching EOF. They closed the readline interface but did not explicitly destroy and await closure of the underlying ReadStream. On Windows, that stream could retain a file handle long enough to make an immediate atomic replacement fail with WinError 5.

Impact

After read_file returns a prefix of a large text file, another process can immediately replace that path without being blocked by a handle retained by Desktop Commander.

Fixes #476.

Validation

  • npm run build
  • node test/test-read-file-handle-release.js
  • node test/test-file-handlers.js
  • node test/test-read-abort-timeout.js (4/4)

Summary by CodeRabbit

  • Bug Fixes

    • Improved file-reading cleanup to deterministically close readline and underlying streams, improving file-handle release after bounded/sliced reads.
    • Enhanced stability for reads from the beginning, end, and estimated positions, reducing cases where files could remain locked and block atomic replacement.
  • Tests

    • Added an end-to-end test ensuring truncated reads correctly release handles by verifying atomic rename/replace succeeds for both prefix and suffix reads.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca1238b9-9023-40b4-9819-589166d564ad

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Text file reads now close readline interfaces and underlying streams across end-based, start-based, and estimated-position paths. An end-to-end test verifies truncated reads release handles before file replacement.

Changes

Read stream cleanup

Layer / File(s) Summary
Coordinate readline and stream closure
src/utils/files/text.ts
Adds shared cleanup that destroys and awaits underlying streams, and applies it through guarded async iteration in all affected read paths.
Validate truncated-read handle release
test/test-read-file-handle-release.js
Tests bounded and suffix reads, atomic replacement, post-replacement contents, and temporary-state cleanup.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 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 summarizes the main fix: releasing file handles after truncated reads.
Linked Issues check ✅ Passed The stream cleanup changes and regression test address the Windows handle leak described in #476.
Out of Scope Changes check ✅ Passed The patch stays focused on truncated-read cleanup and its regression test, with no unrelated changes.
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.

@Soumyajit2288
Soumyajit2288 marked this pull request as ready for review July 11, 2026 07:32
@Soumyajit2288

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/files/text.ts (1)

323-356: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

readFromEndWithReadline still leaks the stream handle.

The stream variable was introduced (lines 323–325) but cleanup at line 339 still calls rl.close() directly instead of closeReadlineStream(rl, stream). The stream is never explicitly destroyed and its close event is never awaited, so on Windows the file handle may still be open when the function returns — the exact bug this PR fixes.

Additionally, there is no try/finally around the for await loop. If an error occurs mid-iteration (e.g., AbortSignal aborts), rl.close() at line 339 is skipped entirely, leaking both the readline interface and the underlying stream.

🔒 Proposed fix: wrap in try/finally and use closeReadlineStream
         const buffer: string[] = new Array(requestedLines);
         let bufferIndex = 0;
         let totalLines = 0;

-        for await (const line of rl) {
-            buffer[bufferIndex] = line;
-            bufferIndex = (bufferIndex + 1) % requestedLines;
-            totalLines++;
+        try {
+            for await (const line of rl) {
+                buffer[bufferIndex] = line;
+                bufferIndex = (bufferIndex + 1) % requestedLines;
+                totalLines++;
+            }
+        } finally {
+            await closeReadlineStream(rl, stream);
         }

-        rl.close();
-
         let result: string[];
🤖 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/utils/files/text.ts` around lines 323 - 356, Update
readFromEndWithReadline to wrap the for-await loop and result processing in
try/finally, and perform cleanup in the finally block with
closeReadlineStream(rl, stream) instead of calling rl.close() directly. Ensure
cleanup runs for successful reads and iteration errors or aborts before the
function exits.
🤖 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.

Outside diff comments:
In `@src/utils/files/text.ts`:
- Around line 323-356: Update readFromEndWithReadline to wrap the for-await loop
and result processing in try/finally, and perform cleanup in the finally block
with closeReadlineStream(rl, stream) instead of calling rl.close() directly.
Ensure cleanup runs for successful reads and iteration errors or aborts before
the function exits.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 80f0c028-3411-4f0d-b35c-19cc7b50b1a1

📥 Commits

Reviewing files that changed from the base of the PR and between 78100e6 and 1e8d832.

📒 Files selected for processing (2)
  • src/utils/files/text.ts
  • test/test-read-file-handle-release.js

@Soumyajit2288

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

Windows: large-file prefix reads can leave a handle open and block later overwrite in the same Desktop Commander session

1 participant