6.3 KiB
gh-10207 — File tree falls back to lazy loading on ExceededMaxFileLimit
Context
Issue #10207: Project Explorer
silently shows populated folders (e.g. .agents) as empty when a repo exceeds
MAX_FILES_PER_REPO (100,000) during initial indexing. The reporter has
~153k tracked files in ~/code and sees Failed to build file tree for repository: ExceededMaxFileLimit in the log.
Root cause:
crates/repo_metadata/src/local_model.rs:892runsEntry::build_treewithMAX_TREE_DEPTH=200and a 100k-file global quota (crates/repo_metadata/src/local_model.rs:60).- On
ExceededMaxFileLimit(crates/repo_metadata/src/entry.rs:14-28, raised inentry.rs:157-220), the spawn callback marks the repoIndexedRepoState::Failed. The view (app/src/code/file_tree/view.rs:1584-1586) swaps aFailedstate for an emptyFileTreeEntry. - Lazy per-directory expansion already exists (
Entry::load,entry.rs:257-284, withLAZY_LOAD_FILE_LIMIT=5000) but is unreachable when the root isFailed.
Scope agreed with @moirahuang and @alokedesai (comment): make the file tree use lazy loading when indexing hits the maximum, and otherwise leave the user experience unchanged. No toast, no indicator — the visible result should be "the folder expands" rather than "the folder expands plus a banner." A broader "always-lazy file tree" exploration is tracked internally as a follow-up.
Out of scope: repo-local skill discovery
(app/src/ai/skills/file_watchers/) silently drops skills in degraded
mode because it queries the metadata tree, and ai::project_context::model
(crates/ai/src/project_context/model.rs:298), warp::ai::outline::native,
and ai::index::full_source_code_embedding each call Entry::build_tree
independently and hit the same hard limit. Per @moirahuang
(comment),
those surfaces will be addressed holistically in follow-up work; this
spec is strictly scoped to the file tree.
Proposed changes
File tree fallback (load-bearing)
In the index_repository spawn callback (local_model.rs:892-1030), on
Err(BuildTreeError::ExceededMaxFileLimit), retry Entry::build_tree once
with:
max_depth = 1so the root is loaded with unloaded subdirectory entries (entry.rs:142-150).remaining_file_quota = None. Direct-child files at depth=1 consume the quota (entry.rs:214-220), so reusingMAX_FILES_PER_REPOwould re-triggerExceededMaxFileLimiton the rare repo with >100k files directly under the root and reproduce the empty-tree bug. Cost is bounded by root-entry count since subdirectories return as unloaded immediately at depth=1; only top-level files allocateFileMetadata.
On retry success, install the repo as IndexedRepoState::Indexed via the
existing add_repository_internal, which emits the usual
RepositoryUpdated event. The view's existing handlers refresh the tree;
the existing per-directory lazy-load path (view.rs:1410-1431,
LAZY_LOAD_FILE_LIMIT=5000) handles expansion from there.
On retry failure (e.g. IOError reading the root directory), keep the
existing mark_repository_failed path. This is the same outcome users
already get for unreadable repo roots and is outside the scope of #10207.
No new event, no new state on FileTreeState, no UI plumbing in the view.
Testing and validation
- Unit (regression for the original empty-tree bug): drive
Entry::build_treepastMAX_FILES_PER_REPO(parameterized via a test-only constructor on the model or by lowering the constant under#[cfg(test)]) and assert the resultingIndexedRepoState::Indexedwith the root containing depth-1 unloaded subdirectory entries. AssertRepositoryUpdatedfires andUpdatingRepositoryFaileddoes not. - Unit (regression for the depth-1 quota gap, requires the
Nonequota fix): fixture withMAX_FILES_PER_REPO + 1files placed directly under the repo root. Without the unquoted retry the fallback reproduces the original bug. Withremaining_file_quota = Nonethe test assertsIndexedwith all top-level files present. - Manual: use the existing fixture at
~/code-fixtures/warp-10207-large-repo(150,001 files). Build with./script/run --dont-openandopen -a target/debug/bundle/osx/WarpOss.app ~/code-fixtures/warp-10207-large-repo. Verify:~/Library/Logs/warp-oss.logcontains the "indexed in degraded mode" warn line (or whatever the implementation logs — non-empty, non-error)..agents,src, etc. expand and show their contents.- No toast, no banner. UI should look like any other repo.
Risks and mitigations
- Watcher pressure on 153k-file repos. The recursive
register_pathcall already happens today for any indexed repo; this PR does not change the watcher footprint. If users report watcher CPU/memory issues, a follow-up could prune the watch to actually-loaded subtrees. - Silent degradation for non-file-tree surfaces. The team's explicit position is that the file tree experience should not change, so there is no UI signal here that the repo is in degraded mode. Repo-local skill discovery, project rules, outline, and codebase indexing each hit the same limit on their own and will go silent or partially silent in degraded mode. They are explicitly out of scope for this spec and will be addressed holistically in follow-up work.
Follow-ups
- Always-lazy file tree (tracked internally by @moirahuang /
@alokedesai): if the file tree is moved to lazy loading by default,
the special-case fallback in this spec collapses into the default
path and
MAX_FILES_PER_REPOcan be dropped from the file-tree pipeline. - Telemetry: a counter on the depth-1 fallback so we can see how often
it fires in the wild. The existing
RepoMetadataTelemetryEvent::BuildTreeFailed { error: "ExceededMaxFileLimit" }atlocal_model.rs:1001already covers the trigger side; pair it with a success counter on the fallback retry. - Holistic handling of degraded-mode behavior for repo-local skill
discovery,
ai::project_context::model,ai::outline::native, andai::index::full_source_code_embedding. Tracked separately by @moirahuang.