Skip to content

Refactor the read tool to better handle truncation#16

Open
hbmartin wants to merge 3 commits into
mainfrom
refactor-read-tool
Open

Refactor the read tool to better handle truncation#16
hbmartin wants to merge 3 commits into
mainfrom
refactor-read-tool

Conversation

@hbmartin
Copy link
Copy Markdown
Owner

@hbmartin hbmartin commented Aug 19, 2025

Fixed Issues:

  1. Clamping to valid ranges: startLine and lineCount are now properly clamped to positive integers using Math.max(Math.floor(...), min_value)
  2. Proper truncation detection: The code now distinguishes between:
    - User explicitly requesting 0/few lines (requestedLineCount !== undefined)
    - System limiting due to default constraints (requestedLineCount === undefined)
  3. Conditional truncation headers: The "Content truncated" message only appears when we actually truncated content, not when the user explicitly asked for
    fewer lines
  4. Conditional linesShown: The linesShown metadata is only included when selectedLines.length > 0, preventing confusing ranges like [1, 0]

Key Improvements:

  • lineCount=0 now returns empty content without truncation headers
  • Negative values are clamped to valid ranges
  • No more misleading "lines 1-0" displays
  • Cleaner UX when users request specific line ranges

Summary by Sourcery

Refactor text processing in read-tool to validate and clamp line range parameters and accurately report content truncation

Enhancements:

  • Clamp and floor startLine and lineCount inputs to ensure valid integer ranges
  • Differentiate between default and explicit truncation limits to avoid misleading notices
  • Only emit truncation warnings and metadata.linesShown when lines are actually returned

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Warning

Rate limit exceeded

@hbmartin has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2a148 and 5559e51.

📒 Files selected for processing (1)
  • src/tools/read-tool.ts (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-read-tool

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Aug 19, 2025

Reviewer's Guide

Refactors processTextFile to enforce robust startLine and lineCount clamping, differentiate between default- and user-driven truncation, conditionally render notices, and streamline metadata construction.

Class diagram for updated processTextFile metadata construction

classDiagram
    class FileReadResult {
        +number lineCount
        +boolean isTruncated
        +[number, number] linesShown
    }
    class PartialFileReadResult {
        +number lineCount
        +boolean isTruncated
        +[number, number] linesShown
    }
    processTextFile --> PartialFileReadResult : returns metadata
    PartialFileReadResult <|-- FileReadResult
Loading

Flow diagram for improved truncation handling in processTextFile

flowchart TD
    A[Start: Receive content, startLine, lineCount] --> B[Clamp startLine and lineCount]
    B --> C[Slice lines based on clamped values]
    C --> D[Truncate long lines]
    D --> E[Determine if content was truncated]
    E --> F{Was content truncated?}
    F -- Yes --> G[Add truncation notice]
    F -- No --> H[Skip truncation notice]
    G --> I[Build metadata]
    H --> I[Build metadata]
    I --> J[Return processed content and metadata]
Loading

File-Level Changes

Change Details Files
Add input validation and clamping for startLine and lineCount
  • Clamp startLine to a positive integer and convert to 0-based index
  • Clamp lineCount to a non-negative integer or default to max lines
  • Use Math.floor to handle non-integer inputs
src/tools/read-tool.ts
Refine content-truncation detection logic
  • Introduce wasLimitedByDefault and wasLimitedByFileSize flags
  • Compute contentWasTruncated only when limits are hit by default or file size
src/tools/read-tool.ts
Conditionally render truncation notices
  • Only add content-truncated notice if contentWasTruncated and lines are returned
  • Differentiate between content vs. overlong-line truncation notices
src/tools/read-tool.ts
Streamline metadata construction
  • Build a metadata object with optional linesShown only when lines are returned
  • Remove inline metadata literal in return value
src/tools/read-tool.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @hbmartin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the read tool to significantly improve its handling of content truncation. The changes focus on making the line range processing more robust by adding comprehensive validation and clamping for startLine and lineCount parameters. Furthermore, the logic for determining and reporting content truncation has been refined, ensuring that truncation notices and metadata accurately reflect whether content was limited by system constraints or explicit user requests. This enhances the reliability and clarity of the tool's output when dealing with partial file content.

Highlights

  • Improved Line Range Handling: Implemented more robust validation and clamping for startLine and lineCount parameters, ensuring they are treated as positive integers and correctly converted to 0-based indices. This prevents unexpected behavior when invalid or edge-case line range inputs are provided.
  • Refined Truncation Logic: Updated the contentWasTruncated determination to differentiate between truncation occurring due to default maximum line limits and truncation resulting from an explicit user request for a specific number of lines. This leads to more accurate reporting of whether the content was truly limited by the tool's constraints.
  • Conditional Truncation Notices and Metadata: Modified the display of truncation notices and the inclusion of linesShown in the returned metadata. These elements now only appear when content is actually returned and has been truncated, preventing misleading messages or empty linesShown arrays when no lines are selected (e.g., if lineCount was explicitly set to 0).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

When an explicit 0 lineCount is requested, the function still prepends a "Content truncated" header if DEFAULT_MAX_LINES is less than the file size, even though contentWasTruncated logic seems intended to avoid marking user-limited requests as truncated. Verify that zero-line requests never include a truncation header and that isTruncated reflects only system-imposed limits.

// Add truncation notice only when we actually truncated content
if (contentWasTruncated && selectedLines.length > 0) {
    processedContent = `[Content truncated: showing lines ${actualStartLine + 1}-${endLine} of ${originalLineCount} total lines]\n\n${processedContent}`;
} else if (linesWereTruncated) {
    processedContent = `[Some lines truncated due to length (max ${MAX_LINE_LENGTH} chars)]\n\n${processedContent}`;
}
Edge Case

Clamping startLine to 1 may be surprising if callers pass 0 intending to use 0-based; confirm API contract. Also, when startLine > originalLineCount, selectedLines is empty and no linesShown is emitted—ensure this is the desired metadata behavior.

// Handle line range with proper validation and clamping
const clampedStartLine = Math.max(Math.floor(startLine ?? 1), 1); // Ensure positive integer, 1-based
const actualStartLine = Math.max(clampedStartLine - 1, 0); // Convert to 0-based

// Clamp lineCount to non-negative integer, with special handling for explicit 0
const requestedLineCount =
    lineCount !== undefined ? Math.max(Math.floor(lineCount), 0) : undefined;
const actualLineCount = requestedLineCount ?? Math.min(DEFAULT_MAX_LINES, originalLineCount);

const endLine = Math.min(actualStartLine + actualLineCount, originalLineCount);
const selectedLines = lines.slice(actualStartLine, endLine);

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown

@ellipsis-dev ellipsis-dev Bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to ddeda16 in 42 seconds. Click for details.
  • Reviewed 72 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 4 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. src/tools/read-tool.ts:157
  • Draft comment:
    Good improvement: clamping startLine with Math.floor and Math.max ensures a valid 1-based input before conversion to 0-based.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
2. src/tools/read-tool.ts:161
  • Draft comment:
    Clamping the lineCount with Math.floor and Math.max to ensure a non-negative integer is robust.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
3. src/tools/read-tool.ts:178
  • Draft comment:
    The new truncation logic using wasLimitedByDefault and wasLimitedByFileSize clarifies when content is truncated; a neat improvement.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
4. src/tools/read-tool.ts:197
  • Draft comment:
    Building metadata conditionally (adding linesShown only when content exists) cleans up the output.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_V4vDC4MvTga6KzSy

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Aug 19, 2025

PR Code Suggestions ✨

Latest suggestions up to 5559e51

CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Handle start past EOF early

Guard against actualStartLine being greater than or equal to originalLineCount
to avoid misleading metadata ranges and unnecessary processing when the
requested start is past EOF; short-circuit to return empty content with correct
linesShown omitted in that case

src/tools/read-tool.ts [157-166]

 const clampedStartLine = Math.max(Math.floor(startLine ?? 1), 1); // Ensure positive integer, 1-based
 const actualStartLine = clampedStartLine - 1; // Convert to 0-based
+
+// If start is past EOF, return empty selection early
+if (actualStartLine >= originalLineCount) {
+    return {
+        content: '',
+        metadata: {
+            lineCount: originalLineCount,
+            isTruncated: false,
+        },
+    };
+}
 
 // Clamp lineCount to non-negative integer, with special handling for explicit 0
 const requestedLineCount =
     lineCount !== undefined ? Math.max(Math.floor(lineCount), 0) : undefined;
 const actualLineCount = requestedLineCount ?? Math.min(DEFAULT_MAX_LINES, originalLineCount);
 
 const endLine = Math.min(actualStartLine + actualLineCount, originalLineCount);
 const selectedLines = lines.slice(actualStartLine, endLine);
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies an edge case where startLine is beyond the end of the file, and provides a robust fix by short-circuiting, which simplifies logic and prevents potential issues with metadata reporting.

Medium
Possible issue
Fix truncation detection window

The truncation logic can falsely report truncation when startLine shifts the
window near EOF. Compare against the available tail from actualStartLine to
avoid mislabeling. Use remaining lines from actualStartLine as the reference for
both default and requested limits.

src/tools/read-tool.ts [178-186]

 // Only consider content truncated if we hit the limit due to our own constraints,
 // not when the user explicitly requested fewer/zero lines
-const wasLimitedByDefault = requestedLineCount === undefined && selectedLines.length < originalLineCount;
-const wasLimitedByFileSize =
-    requestedLineCount !== undefined &&
-    requestedLineCount > 0 &&
-    selectedLines.length < requestedLineCount;
-const contentWasTruncated = wasLimitedByDefault || wasLimitedByFileSize;
+const remainingFromStart = Math.max(originalLineCount - actualStartLine, 0);
+const wasLimitedByDefault =
+  requestedLineCount === undefined &&
+  selectedLines.length < remainingFromStart &&
+  selectedLines.length >= Math.min(DEFAULT_MAX_LINES, remainingFromStart);
+const wasLimitedByRequestedCount =
+  requestedLineCount !== undefined &&
+  requestedLineCount > 0 &&
+  selectedLines.length < Math.min(requestedLineCount, remainingFromStart);
+const contentWasTruncated = wasLimitedByDefault || wasLimitedByRequestedCount;
 const isTruncated = contentWasTruncated || linesWereTruncated;
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a subtle bug where contentWasTruncated could be incorrectly flagged as true when startLine is near the end of the file, because the logic does not account for the reduced number of available lines. The proposed fix correctly calculates truncation against the remaining lines, which is a significant improvement in correctness.

Medium
  • More

Previous suggestions

Suggestions up to commit 5559e51
CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Correct false-positive truncation flag

Ensure truncation detection accounts for an out-of-range start causing
selectedLines.length to be 0 without truncation; explicitly treat
actualStartLine >= originalLineCount as not truncated

src/tools/read-tool.ts [180-186]

 // Only consider content truncated if we hit the limit due to our own constraints,
-// not when the user explicitly requested fewer/zero lines
-const wasLimitedByDefault = requestedLineCount === undefined && selectedLines.length < originalLineCount;
+// not when the user explicitly requested fewer/zero lines or start is out of range
+const startOutOfRange = actualStartLine >= originalLineCount;
+const wasLimitedByDefault =
+    !startOutOfRange &&
+    requestedLineCount === undefined &&
+    selectedLines.length < originalLineCount;
 const wasLimitedByFileSize =
+    !startOutOfRange &&
     requestedLineCount !== undefined &&
     requestedLineCount > 0 &&
     selectedLines.length < requestedLineCount;
 const contentWasTruncated = wasLimitedByDefault || wasLimitedByFileSize;
 const isTruncated = contentWasTruncated || linesWereTruncated;
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies and fixes a bug where contentWasTruncated could be wrongly set to true if the user provides a startLine that is beyond the end of the file, leading to an incorrect truncation message.

Medium
Possible issue
Clamp start beyond EOF safely

Guard against actualStartLine exceeding originalLineCount to avoid off-by-range
slicing and misleading metadata when startLine is beyond EOF. Clamp the start to
the file length and produce empty content while reflecting no lines shown. This
prevents negative or inverted ranges and keeps linesShown consistent.

src/tools/read-tool.ts [157-165]

 const clampedStartLine = Math.max(Math.floor(startLine ?? 1), 1); // Ensure positive integer, 1-based
-const actualStartLine = clampedStartLine - 1; // Convert to 0-based
+let actualStartLine = clampedStartLine - 1; // Convert to 0-based
 
 // Clamp lineCount to non-negative integer, with special handling for explicit 0
 const requestedLineCount =
     lineCount !== undefined ? Math.max(Math.floor(lineCount), 0) : undefined;
 const actualLineCount = requestedLineCount ?? Math.min(DEFAULT_MAX_LINES, originalLineCount);
 
+// If start is beyond EOF, clamp to originalLineCount so slice returns empty and end matches start
+if (actualStartLine > originalLineCount) {
+  actualStartLine = originalLineCount;
+}
 const endLine = Math.min(actualStartLine + actualLineCount, originalLineCount);
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies an edge case where a startLine beyond the end of the file would lead to an invalid linesShown range in the metadata, and provides a robust fix.

Medium
General
Show notice for empty truncations

When no lines are returned (e.g., lineCount is 0 or startLine beyond EOF) but
truncation occurred, add a clear notice instead of suppressing it. This avoids
silent truncation and provides consistent feedback to the caller.

src/tools/read-tool.ts [190-195]

 // Add truncation notice only when we actually truncated content
-if (contentWasTruncated && selectedLines.length > 0) {
+if (contentWasTruncated) {
+  if (selectedLines.length > 0) {
     processedContent = `[Content truncated: showing lines ${actualStartLine + 1}-${endLine} of ${originalLineCount} total lines]\n\n${processedContent}`;
+  } else {
+    processedContent = `[Content truncated: no lines shown (requested range exceeds limits) of ${originalLineCount} total lines]\n\n${processedContent}`;
+  }
 } else if (linesWereTruncated) {
-    processedContent = `[Some lines truncated due to length (max ${MAX_LINE_LENGTH} chars)]\n\n${processedContent}`;
+  processedContent = `[Some lines truncated due to length (max ${MAX_LINE_LENGTH} chars)]\n\n${processedContent}`;
 }
Suggestion importance[1-10]: 7

__

Why: This suggestion improves user experience by adding a clear truncation notice when no lines are returned due to range limits, which is a valuable enhancement for API clarity.

Medium
Suggestions up to commit ddeda16
CategorySuggestion                                                                                                                                    Impact
High-level
Zero-line request behavior ambiguity

When lineCount is explicitly set to 0, the function still reads and splits the
entire file, then returns empty content without a clear, user-facing notice or a
distinct metadata signal. Consider short-circuiting early (before
reading/splitting large files) and returning an explicit, non-truncation status
with clear metadata/reason to avoid unnecessary I/O and confusion between
user-requested emptiness and system truncation.

Examples:

src/tools/read-tool.ts [152-163]
    const content = await fs.promises.readFile(filePath, encoding as BufferEncoding);
    const lines = content.split('\n');
    const originalLineCount = lines.length;

    // Handle line range with proper validation and clamping
    const clampedStartLine = Math.max(Math.floor(startLine ?? 1), 1); // Ensure positive integer, 1-based
    const actualStartLine = Math.max(clampedStartLine - 1, 0); // Convert to 0-based

    // Clamp lineCount to non-negative integer, with special handling for explicit 0
    const requestedLineCount =

 ... (clipped 2 lines)

Solution Walkthrough:

Before:

async function processTextFile(filePath, startLine, lineCount) {
    // File is always read and split, regardless of lineCount.
    const content = await fs.promises.readFile(filePath);
    const lines = content.split('\n');
    const originalLineCount = lines.length;

    // Logic to handle lineCount=0 happens after the file read.
    const requestedLineCount = lineCount === 0 ? 0 : ...;
    const actualLineCount = requestedLineCount ?? ...;

    // ... proceeds to return empty content and metadata
}

After:

async function processTextFile(filePath, startLine, lineCount) {
    // Add an early return for lineCount=0 to prevent I/O.
    if (lineCount === 0) {
        return {
            content: '',
            metadata: {
                lineCount: null, // Total lines unknown without reading
                isTruncated: false,
                notice: 'Returned 0 lines as requested.'
            }
        };
    }

    // File is read only when necessary.
    const content = await fs.promises.readFile(filePath);
    // ... rest of the logic
}
Suggestion importance[1-10]: 8

__

Why: This is a strong suggestion that correctly identifies a significant performance optimization (avoiding unnecessary file I/O) and an API clarity improvement, which aligns with the PR's goal of better handling edge cases.

Medium
Possible issue
Prevent NaN from breaking slicing

Guard against NaN when startLine or lineCount are non-numeric. If Math.floor
receives NaN, actualStartLine or actualLineCount can become NaN, causing slice
to behave unexpectedly. Add explicit fallbacks to safe defaults before computing
endLine and slicing.

src/tools/read-tool.ts [165-166]

-const endLine = Math.min(actualStartLine + actualLineCount, originalLineCount);
+const safeStart = Number.isFinite(actualStartLine) ? actualStartLine : 0;
+const safeCount = Number.isFinite(actualLineCount) ? actualLineCount : 0;
+const endLine = Math.min(safeStart + safeCount, originalLineCount);
 
-const selectedLines = lines.slice(actualStartLine, endLine);
+const selectedLines = lines.slice(safeStart, endLine);
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that if startLine or lineCount are NaN, it could lead to unexpected behavior. Adding checks with Number.isFinite improves the function's robustness against invalid numeric inputs.

Low
Sanitize indices in metadata

Ensure linesShown uses sanitized indices to avoid propagating NaN or negative
values. Use the same safe indices used for slicing so metadata remains
consistent and valid.

src/tools/read-tool.ts [203-205]

 if (selectedLines.length > 0) {
-    metadata.linesShown = [actualStartLine + 1, endLine];
+    const safeStart = Number.isFinite(actualStartLine) ? actualStartLine : 0;
+    const safeEnd = Number.isFinite(endLine) ? endLine : Math.max(0, Math.min(originalLineCount, safeStart));
+    metadata.linesShown = [safeStart + 1, safeEnd];
 }
Suggestion importance[1-10]: 6

__

Why: This suggestion correctly points out a potential issue where NaN values for actualStartLine or endLine could be propagated to the metadata. Sanitizing these values ensures the returned metadata is always valid.

Low
General
Fix misleading variable naming

The variable name implies file size limitation, but the logic checks line count
limits; this is misleading and error-prone. Rename the flag to reflect
line-limit behavior to avoid future misuse and misinterpretation.

src/tools/read-tool.ts [181-184]

-const wasLimitedByFileSize =
+const wasLimitedByRequestedLineLimit =
     requestedLineCount !== undefined &&
     requestedLineCount > 0 &&
     endLine < actualStartLine + requestedLineCount;
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that the variable name wasLimitedByFileSize is misleading as the logic is based on line counts, not file size in bytes. This improves code clarity and maintainability.

Low

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the file reading tool by improving line range validation and providing more nuanced truncation detection. The changes are a good step forward in making the tool more robust. However, I've identified a significant bug in the new truncation logic that fails in certain scenarios, which could lead to confusing output for the user. I've also included a suggestion to improve code clarity. Addressing the bug is important for this feature to work as intended.

Comment thread src/tools/read-tool.ts Outdated
Comment thread src/tools/read-tool.ts Outdated
hbmartin and others added 2 commits August 18, 2025 17:56
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Sep 3, 2025

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant