Skip to content

fix(fs): walk directories in path order so prefetch matches consumers - #2821

Merged
chaliy merged 1 commit into
mainfrom
claude/project-thread-w2g6tc
Nov 3, 2026
Merged

chaliy merged 1 commit into
mainfrom
claude/project-thread-w2g6tc

Conversation

@chaliy

@chaliy chaliy commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

What changed

The default FileSystem::walk now visits a directory's children in name order. Before, it used read_dir order, which is hash order for InMemoryFs.

Why

tree, ls -R and find consume listings in path order. When a walk batch was cut short by max_entries (for example, tree capping it at its remaining entry budget), it held arbitrary listings instead of the next ones the caller needed, so it read directories that were never printed. Because the order changed from run to run, tree_prefetch_stays_within_entry_budget was flaky, and it failed the coverage job on main at 6d66d43.

Before / After

tree tree with max_commands(50) on the round-trip fixture:

  • Before: read_dir = 8, 12, 13, 15 across runs (CI coverage failed at 15).
  • After: read_dir = 6 on every run. The test now asserts <= 6.

The new unit test default_walk_visits_directories_in_path_order failed before the fix and passes after it.

Risk

  • Low. One sort of directory names per listed directory. Output of every walker is unchanged, because each one sorts its own output.

Checklist

  • Tests added or updated
  • Backward compatibility considered

Generated by Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

The default walk followed read_dir order (hash order for InMemoryFs), so a
batch cut short by max_entries held listings tree/ls -R/find would not
reach next. tree_prefetch_stays_within_entry_budget read 8-15 directories
run to run and failed main's coverage job; it now reads 6 every time.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 5692dc1 Commit Preview URL

Branch Preview URL
Nov 03 2026, 08:14 AM

@chaliy
chaliy merged commit 7a8c3f8 into main Nov 3, 2026
54 checks passed
@chaliy
chaliy deleted the claude/project-thread-w2g6tc branch November 3, 2026 18:47

@bashkit-pr-reviewer bashkit-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Correct fix for the flaky prefetch: deterministic DFS in consumer order, with consumers verified (tree sorts + recurses ascending, find sorts + pushes reversed for ascending pop, ls -R iterates subdirs in listed order). No sandbox/security impact (sort only, no new I/O). One residual nit below.

pending.push((super::vfs_join(&path, &entry.name), child_depth, None));
}
// Visit children in name order (pushed reversed, popped first),
// the order tree/ls -R/find consume them: a batch cut short by

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] ls -R -t still consumes in mtime order while this prefetches in name order, so the "holds the next listings the caller needs" claim doesn't hold for that flag. Evidence: crates/bashkit/src/builtins/ls/list.rs:361-366 sorts sorted_entries by modified when sort_by_time, but list.rs:297 builds WalkCache::new(opts.recursive) unconditionally, and subdirs follows the time-sorted order. Correctness is unaffected (cache misses fall back to read_dir), just extra round trips on large ls -R -t runs. Consider scoping prefetch to the name-order case (e.g. WalkCache::new(opts.recursive && !opts.sort_by_time)) or softening this comment to "the order tree/ls -R (name order)/find consume them".

@bashkit-pr-reviewer

Copy link
Copy Markdown

Review summary (PR #2821, 5692dc1)

Verdict: sound fix, no blockers. Deterministic depth-first walk in name order matches the three consumers' traversal, which fixes the flaky tree_prefetch_stays_within_entry_budget (was 8–15 read_dirs run to run, now 6). One P2 nit posted as an inline comment.

Findings: 1 × P2 (walk-prefetch-ls-t-order): ls -R -t consumes in mtime order (ls/list.rs:361-366) but WalkCache prefetches unconditionally in name order (ls/list.rs:297), so a cut-short batch can still miss for that flag. Correctness unaffected (fallback to read_dir); suggest gating prefetch on !sort_by_time or narrowing the comment.

Context read beyond the diff: fs/traits.rs:515-560 (walk_with_read_dir, incl. reversed-push-then-pop ascending DFS), all walk overrides (follow/mountable/rootfs/std_streams delegate; counting_fs test double delegates), consumer order (tree.rs:274 sort + ascending recursion, find/mod.rs:924 sort + stack.extend(rev) at 878/1021, ls/list.rs:360-366 sort + subdirs in listed order), walk_cache.rs bounded-batch contract, AGENTS.md index rows, knowledge/foundations/vfs.md round-trips section + knowledge/log.md 2026-11-03 entry.

Could not review: the raw unified diff (get_github_pull_request_diff returned a masked payload), so line anchors come from the checked-out head (/workspace/bashkit) at the PR head SHA; 11 *.wasm/report files were skipped by checkout (irrelevant). No build/test run per workflow (reasoned from code). Note: PR is already merged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant