rtk-ai/rtk

Bug: `rtk grep` misparses single-file rg output when content contains colons

Open

#1613 opened on Apr 29, 2026

View on GitHub
 (2 comments) (1 reaction) (0 assignees)Rust (48,085 stars) (2,914 forks)batch import
bugeffort-smallfilter-qualitygood first issue

Description

Environment

  • RTK: 0.37.2
  • ripgrep: 15.1.0
  • OS: Windows; rtk and rg are installed via scoop

Description

rtk grep searching a single file produces incorrect filenames and line numbers when matched lines contain colons. The parser mistakenly treats the line number as a filename and the content gets truncated.

Steps to Reproduce

# Create test file
cat > test.ts << 'EOF'
function readEnabledFromBranch(ctx: ExtensionContext, fallback: boolean): boolean {
    return true;
}
function simpleNoColon() {
    return 42;
}
EOF

# Run
rtk grep "function" test.ts

Actual Output

2 matches in 2F:

[file] 1 (1):
  0: ExtensionContext, fallback: boolean): boolean {

[file] test.ts (1):
  4: function simpleNoColon() {
  • Line with colons: filename=2 (wrong — this is the line number), line_number=0 (parse failure)
  • Line without colons: correct

Expected Output

2 matches in 1F:

[file] test.ts (2):
     1: function readEnabledFromBranch(ctx: ExtensionContext, fallback: boolean): boolean {
     4: function simpleNoColon() {

Reason

rg Output Format Difference

In grep_cmd.rs, rg is invoked:

rg_cmd.args(["-n", "--no-heading", &rg_pattern, path]);

ripgrep omits the filename prefix when searching a single file. RTK's parser assumes filename is always present.

# Multi-file: filename is ALWAYS included
src/a.ts:2:function foo() { }
src/b.ts:5:function bar() { }
   │         │         │
   ▼         ▼         ▼
 file     linenum    content

# Single-file: filename is OMITTED
2:function readEnabledFromBranch(ctx: ExtensionContext) { }
│         │                          │
▼         ▼                          ▼
linenum   content (contains colons!)

When RTK parses the single-file output with splitn(3, ':'):

Input:  2:function readEnabledFromBranch(ctx: ExtensionContext) { }
                   ↓ splitn(3, ':')
Part:   ["2"]  ["function readEnabledFromBranch(ctx"]  [" ExtensionContext) { }"]
            │              │                                     │
            ▼              ▼                                     ▼
     treated as       treated as                           treated as
      filename         linenum                               content
          ❌             ❌ (fails)                            ✓ (truncated)
let parts: Vec<&str> = line.splitn(3, ':').collect();

let (file, line_num, content) = if parts.len() == 3 {
    let ln = parts[1].parse().unwrap_or(0);
    (parts[0].to_string(), ln, parts[2])  // ← BUG: parts[0] is line number, not filename
} else if parts.len() == 2 {
    let ln = parts[0].parse().unwrap_or(0);
    (path.to_string(), ln, parts[1])      // ← Never reached: colons in content inflate count to 3
};

Suggested Fix

Add --with-filename to force rg to always include the filename prefix:

-H, --with-filename
    This flag instructs ripgrep to print the file path for each matching
    line. This is the default when more than one file is searched. If
    --heading is enabled (the default when printing to a tty), the file
    path will be shown above clusters of matches from each file; otherwise,
    the file name will be shown as a prefix for each matched line.
rg_cmd.args(["-n", "--no-heading", "--with-filename", &rg_pattern, path]);

(BUT I'm not sure if it triggers other potential bug)

Or migrate rtk grep parsing to rg --json to avoid delimiter-based parsing bugs.

Contributor guide