rtk-ai / rtk-ai/rtk

find: existing file as search root returns empty output with exit 0; hidden/gitignored matches silently dropped

Open
#2,995 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:cli bug help wanted priority:high
Dominant language
Rust
Stars
81.1k
Forks
5.1k
Avg merge
4d 21h
Merged PRs (30d)
35

Description

Summary

rtk find produces false negatives with exit 0 in three related ways:

  1. An existing file passed as the search root returns empty outputrtk find <file> -type f finds nothing where native find prints the file.
  2. A bare file argument (rtk find <file>, RTK syntax) is parsed as a glob pattern, so it can never match either.
  3. Pattern searches silently drop hidden and gitignored matches, with no indication that pruning happened.

All three exit 0 and (as of 0.43.0) print nothing at all on zero matches. For an LLM agent consuming rtk output as ground truth, "no output, exit 0" reads as "the file does not exist". This bit us in production agent use: an agent ran rtk find <path-to-existing-file>.md -type f, got empty output, concluded the file was missing, and kicked off a large, expensive wrong-turn recovery. Same silent-data-loss class as #2944.

Environment

  • rtk 0.43.0 (Homebrew bottle), macOS arm64 (Darwin 27)
  • Also reproduced on 0.39.0 — there the empty-result cases at least printed 0 for '*' / 0 for '<pattern>'; 0.43.0 prints nothing, which is harder to notice.

Minimal reproduction

mkdir -p /tmp/rtk-find-repro/sub && cd /tmp/rtk-find-repro
echo hello > journal.md && echo world > sub/note.md
git init -q . && echo "sub/" > .gitignore
mkdir -p .hidden && echo x > .hidden/secret.md

# 1) Explicit file path as search root
rtk find ./journal.md -type f    # → (no output), exit 0   ❌
find ./journal.md -type f        # → ./journal.md          ✅

# 2) Bare file argument (RTK syntax)
rtk find ./journal.md            # → (no output), exit 0   ❌

# 3) Hidden + gitignored matches silently dropped
rtk find . -iname '*.md'         # → journal.md  (1 file)  ❌
find . -iname '*.md'             # → 3 files               ✅

Root cause (as of 5d32d07, src/cmds/system/find_cmd.rs)

1. Root-entry drop in run(). Display paths are computed by stripping the search root:

let display_path = entry_path
    .strip_prefix(path)
    .unwrap_or(entry_path)
    .to_string_lossy()
    .to_string();

if !display_path.is_empty() {
    files.push(display_path);
}

When the root is the file (native-syntax parse puts args[0] into parsed.path), the walker yields the root entry, strip_prefix returns "", and the !display_path.is_empty() guard discards the only match. Sibling symptom of the same line: rtk find <dir> -type d omits the root directory itself, which native find prints.

2. Bare file argument parsed as pattern. Without native flags, parse_rtk_find_args takes args[0] as the glob pattern and searches .. A path like ./journal.md is then glob-matched against basenames and can never match. On 0.39.0 the output 0 for './journal.md' at least exposed the misparse; 0.43.0 prints nothing.

3. Hardcoded ignore-pruning. run() builds the walker with:

builder
    .hidden(!search_hidden)   // skip hidden unless pattern starts with '.'
    .git_ignore(true)
    .git_global(true)
    .git_exclude(true);

Deliberate as a default (per the #1101 comment), but under native-flag syntax — which the help text advertises as "accepts native find flags like -name, -type" — the consumer asked for find semantics and silently gets ripgrep semantics. Nothing in the output signals that matches were pruned.

4. Empty result prints nothing. The files.is_empty() branch tracks stats and returns without printing. Silence is indistinguishable from "no files exist", and is a regression from 0.39.0's explicit 0 for '<pattern>'.

Suggested fixes

In descending order of impact:

  1. Handle a file root. If parsed.path is an existing non-directory, short-circuit: apply the type/pattern checks to it directly and print it (or fix display_path for root entries to fall back to the file name / given path instead of "").
  2. Print an explicit zero-result line (0 matches for '<pattern>' in <path>) instead of nothing — restores and improves the pre-0.43 behavior; costs a handful of tokens and prevents "empty = nonexistent" misreads.
  3. Make pruning visible or scoped: when hidden/gitignore filtering drops entries under native-flag syntax, either emit a one-line notice (note: hidden/gitignored entries skipped — use rtk proxy find for the full list) or disable gitignore handling when native find flags are present.
  4. Treat an existing path in args[0] as a path, not a pattern, in parse_rtk_find_args (or reject it with a clear error).

Workaround

For file-existence checks in agent instructions: use test -f <path> or rtk proxy find …. Pattern searches that must honor hidden/gitignored files: rtk proxy find.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/cmds/system/find_cmd.rs, especially run() and parse_rtk_find_args, and reproduce the three cases from the issue on rtk 0.43.0. Trace root-file handling, bare-path parsing, pruning, and empty-result output. Done means existing file roots and bare file arguments produce correct results, pruning behavior is visible or correctly scoped, and zero matches are not silent.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.