fix(tools): list_files returns paths relative to queried directory — closes #24#102
fix(tools): list_files returns paths relative to queried directory — closes #24#102
Conversation
…24 filepath.Rel(".", path) computed paths relative to the process cwd, not the directory the agent requested. Agents listing "internal/agent" got back "internal/agent/loop.go" instead of "loop.go", causing double-prefix errors on follow-up read_file calls. Fix: use filepath.Rel(dir, path) and skip the root "." entry so empty directories return "(empty directory)" correctly. Adds 5 tests covering: relative paths, no-double-prefix regression, extension filter, empty directory, and hidden dir exclusion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes list_files path rendering so returned entries are relative to the directory being listed (not the process CWD), preventing “double-prefix” follow-up paths when agents call read_file on list results.
Changes:
- Compute listed paths via
filepath.Rel(dir, path)(anchored to the queried directory) instead offilepath.Rel(".", path). - Skip the root
"."entry so truly empty directories return"(empty directory)". - Add 5 new
listFilestests covering relative path behavior, regression for double-prefixing, extension filtering, empty directories, and hidden/node_modules exclusion.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/tools/tools.go | Fixes listFiles() relative-path computation and skips the root entry. |
| internal/tools/tools_test.go | Adds test coverage for the corrected listFiles() output behavior and regressions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return nil | ||
| } | ||
| rel, _ := filepath.Rel(".", path) | ||
| rel, _ := filepath.Rel(dir, path) |
There was a problem hiding this comment.
filepath.Rel(dir, path) errors are currently ignored. If Rel fails (e.g., different volume on Windows, malformed input), rel becomes an empty string and the code may append "" or "/" to the output, producing incorrect paths. Handle the error explicitly (e.g., return it to fail the tool call, or skip the entry) before using rel.
| rel, _ := filepath.Rel(dir, path) | |
| rel, errRel := filepath.Rel(dir, path) | |
| if errRel != nil { | |
| return errRel | |
| } |
| // Paths must be relative to dir, not cwd | ||
| for _, line := range strings.Split(r.Output, "\n") { | ||
| if strings.HasPrefix(line, dir) { | ||
| t.Fatalf("got absolute path %q — expected relative to listed dir", line) | ||
| } |
There was a problem hiding this comment.
TestListFiles_PathsRelativeToDirectory only checks that output lines aren’t absolute and don’t start with the temp dir path. With the original bug, entries like "../../tmp/.../a.go" are still relative and would pass these assertions. To actually verify paths are relative to the queried directory, assert that returned paths don’t start with ".." (after filepath.Clean) / don’t contain parent traversals, or add a direct expectation like "sub/b.go" vs any prefixed path.
| // Paths must be relative to dir, not cwd | |
| for _, line := range strings.Split(r.Output, "\n") { | |
| if strings.HasPrefix(line, dir) { | |
| t.Fatalf("got absolute path %q — expected relative to listed dir", line) | |
| } | |
| // Paths must be relative to dir, not cwd, and must not contain parent traversals. | |
| for _, raw := range strings.Split(r.Output, "\n") { | |
| line := strings.TrimSpace(raw) | |
| if line == "" { | |
| continue | |
| } | |
| if strings.HasPrefix(line, dir) { | |
| t.Fatalf("got absolute path %q — expected relative to listed dir", line) | |
| } | |
| clean := filepath.Clean(line) | |
| if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) { | |
| t.Fatalf("got path with parent traversal %q (clean: %q) — expected path relative to listed dir", line, clean) | |
| } |
jpleva91
left a comment
There was a problem hiding this comment.
Reviewer: shellforge:reviewer (automated, 2026-04-02)
Code review: LGTM — would approve if self-review were permitted.
Fix correctness: filepath.Rel(dir, path) is the right anchor — "." resolves to process cwd, not the queried directory, so any absolute-path listing produced double-prefixed paths in the prior code. The rel == "." guard is also necessary: WalkDir visits the root directory itself first, and without this guard that visit emits a spurious . entry.
Tests: 5 targeted tests cover the core regression, no-double-prefix scenario, extension filter, empty dir, and hidden dir exclusion. Good coverage.
CI: All 5 checks passing (build, CodeQL, Analyze ×3).
One pre-existing note (not introduced here): filepath.Rel error is silently discarded. Safe on Linux (only errors cross-volume), but worth a comment if this function grows. Not a blocker.
Blast radius is minimal (2 files, 1 prod line changed). Ready to merge — needs a second reviewer from the team.
Summary
listFiles()calledfilepath.Rel(".", path), computing paths relative to the process cwd instead of the queried directory. Agents listinginternal/agentreceivedinternal/agent/loop.goinstead ofloop.go, causing double-prefix errors on follow-upread_filecalls.filepath.Rel(dir, path)anchored to the requested directory. Also skip the root"."entry so empty directories correctly return"(empty directory)".TestListFiles_*covering relative paths, no-double-prefix regression, extension filter, empty directory, and hidden dir exclusion.Test plan
go build ./...passesgo vet ./...passesgo test ./...— all packages pass (including 5 new listFiles tests)list_filesoutput shows leaf-relative paths in agent sessions🤖 Generated with Claude Code