find: existing file as search root returns empty output with exit 0; hidden/gitignored matches silently dropped
Nobody has claimed this yet.
- 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:
- An existing file passed as the search root returns empty output —
rtk find <file> -type ffinds nothing where nativefindprints the file. - A bare file argument (
rtk find <file>, RTK syntax) is parsed as a glob pattern, so it can never match either. - 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:
- Handle a file root. If
parsed.pathis an existing non-directory, short-circuit: apply the type/pattern checks to it directly and print it (or fixdisplay_pathfor root entries to fall back to the file name / given path instead of""). - 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. - 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. - Treat an existing path in
args[0]as a path, not a pattern, inparse_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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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