Move path-listing policy to the server and list git worktrees via ls-files - #2230
Open
SawyerHood wants to merge 2 commits into
Open
Move path-listing policy to the server and list git worktrees via ls-files#2230SawyerHood wants to merge 2 commits into
SawyerHood wants to merge 2 commits into
Conversation
…files The host daemon's recursive walker behind host.list_files / host.list_paths unconditionally skipped every dot-leading entry, node_modules and symlinks, so quick-open, file search and @-mentions could not find .github/workflows/ci.yml even though host.read_file serves it. The dot rule was also the only thing keeping the walk cheap: dropping it alone descends .venv/.turbo/.next/.cache and makes every keystroke 80-150x slower. Separate policy from primitive: - Contract: host.list_files and host.list_paths gain required includeHidden, excludeNames and respectGitignore fields. Protocol 150 -> 151. - Daemon: the walker takes the policy as arguments, always refuses .git, and stops at a 50k-entry cap (truncated: true). When respectGitignore is set and the root is inside a git worktree, candidates come from `git ls-files -z --cached --others --exclude-standard` with directory entries synthesised from the file paths; non-git roots (and ignored roots) fall back to the capped readdir walk. - Server: workspace search routes fill the product default once (includeHidden true, excludeNames ["node_modules"], respectGitignore true); thread storage takes the disk walk; the two skill consumers pass includeHidden false because their read path denies dotfiles. - Routes/SDK/CLI: includeHidden on environments.paths, projects.paths and files.listPaths; `--no-hidden` on bb environment|project|file paths; guide and bb-cli skill updated. The docs plugin lists its vault without hidden paths since its path contract rejects dot segments. Fixes #2093 Co-Authored-By: Claude <noreply@anthropic.com>
The SDK's PathListArgs is part of the published plugin SDK surface, so the npm version guard requires a new version. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
listPathsRecursivelyinapps/host-daemon/src/command-handlers/file-list.ts, the single walker behindhost.list_filesandhost.list_paths, unconditionally dropped every dot-leading entry,node_modulesand symlinks before looking at the entry kind, and the command schemas carried no knob to relax it. Quick-open, the panel file search and composer @-mentions therefore could not find.github/workflows/ci.yml,.envor anything under.claude/,.bb/,.vscode/, whilehost.read_fileandbb thread openserve the very same file (listing and reading disagreed about whether the file exists). The dot rule was never a product decision; it was a crude stand-in for "skip the stuff nobody wants" and it was also the only thing keeping the uncapped walk cheap. Report: https://get-bb.github.io/reports/issues/2093.html (#2093).Related PRs: #2103 narrows the skip list to
.git/node_modulesin the daemon. That fixes the named symptom but keeps policy hardcoded in the daemon and, because the walker has no budget and runs on every keystroke, descends.venv/.turbo/.next/.cache: the report measuredhost.list_pathson a Python repo going from 6-8 ms to 629-1234 ms per keystroke with.venv/lib/python3.12/site-packages/...pushing the project's ownconfig.pyout of the top results, and the skill-file browser starts listing dotfiles its read path denies. #2127 (Monaco builtin) does not fix this issue; it documents it as a known gap. This PR credits both; what differs is below.What changed
Policy is separated from the primitive: the server owns what is excluded, the daemon owns how it walks cheaply.
packages/host-daemon-contract/src/commands.ts):host.list_filesandhost.list_pathsgain requiredincludeHidden: boolean,excludeNames: string[]andrespectGitignore: boolean(sharedpathListPolicySchema, exportedPathListPolicy).HOST_DAEMON_PROTOCOL_VERSION150 -> 151 with a note inprotocol.ts; an older daemon rejects the new fields, a newer daemon rejects an older server's commands rather than defaulting.file-list.ts,host-files.ts):listRootPathsapplies the policy. WithrespectGitignoreand a root inside a git worktree, candidates come fromgit ls-files -z --cached --others --exclude-standard(tracked + untracked-not-ignored, honours.gitignore,.git/info/excludeand global excludes) through the record-boundedrunGitWithNullRecordLimit(newly exported from@bb/host-workspace); directory entries are synthesised from the file paths and the output is sorted once so parents precede children. Roots outside git, roots that are themselves ignored, hosts without git andrespectGitignore: falsetake the readdir walk, which now takesincludeHidden/excludeNamesas arguments, skips symlinks, and still has exactly one literal: it never lists or descends.git. Both sources stop at a hard 50,000-entry cap (PATH_LIST_ENTRY_LIMIT) and reporttruncated: true.apps/server/src/routes/path-list-policy.tsfills the product default once at the boundary for workspace search (includeHidden: true,excludeNames: ["node_modules"],respectGitignore: true) forenvironments.paths,projects.paths,projects.files,files.listandfiles.listPaths; thread storage (threads/data.ts) usesincludeHidden: truewith the disk walk since a bb-owned data directory has no gitignore semantics. The two skill consumers (workspace-skills.ts,skill-listing.ts) passSKILL_DIRECTORY_LIST_POLICYwithincludeHidden: falsebecausereadProjectSkillreads withdotfiles: "deny".includeHiddenonGET /environments/:id/paths,GET /projects/:id/pathsandPOST /files/paths(omitted = shown);PathListArgs.includeHiddenandEnvironmentPathsArgs.includeHiddenin@bb/sdk;--no-hiddenonbb environment paths,bb project pathsandbb file paths; guide templates (bb-guide-environments.md,bb-guide-projects.md,bb-guide-customization.md) and the bb-cliSKILL.mdupdated perdocs/cli-guide-and-skill.md.plugins/docs/server.ts): its fourfiles.listPathscalls passincludeHidden: falsebecauserequireVaultPathrejects dot segments and the plugin's own.bb-docs-state.jsonlives at the vault root; without this the new default would throw during sync.Deviation from the issue's proposed fix: the issue suggested skipping only
.git(that is #2103) or caller-configurable fields. This implements the fields and additionally makes gitignore the exclusion source plus a walk cap, per the report's section 6, so the wider include set does not trade "the file is not there" for "the search is slow and full of junk". Known trade-offs (documented in code): empty directories do not appear in git mode, a submodule shows as one file-kind entry, and untracked files inside an ignored directory are hidden by default (respectGitignore: falseis on the wire for callers that need them).How you verified
Tests that fail before and pass after:
apps/host-daemon/src/command-handlers/host-files.test.ts: the report's repro —listHostPathswithquery: "ci.yml"returns.github/workflows/ci.ymlandreadHostFileserves the same file; a full listing lists.github/...while a gitignored.venv/lib/ci.ymlis absent;host.list_fileswithincludeHidden: falsehides.DS_Store.apps/host-daemon/src/command-handlers/file-list.test.ts: walker lists dot paths but never.git;includeHidden/excludeNamesprune at the entry; the cap truncates;listRootPathsin a git repo lists tracked + untracked-not-ignored with synthesised directories, keeps.venv/.env/node_modules/.gitout, hides dot paths when told to, walks the disk whenrespectGitignoreis false, falls back outside git and when the root itself is ignored.packages/host-daemon-contract/test/contract.test.ts: protocol 151; a listing command without the policy fields is rejected.apps/server/test/public/public-environments.test.ts,test/files/host-file-routes.test.ts: routes fill the default policy and pass an explicitincludeHidden=falsethrough.test/public/public-project-skills.test.ts,test/threads/thread-runtime-config.test.ts: skill listings sendincludeHidden: falseso they never offer a file thedotfiles: "deny"read rejects.Commands:
pnpm exec turbo run typecheck --filter=@bb/host-daemon-contract --filter=@bb/host-daemon --filter=@bb/server --filter=@bb/server-contract --filter=@bb/sdk --filter=@bb/cli --filter=bb-plugin-simple-notes --filter=@bb/app(clean);pnpm exec turbo run testfor@bb/host-daemon,@bb/host-daemon-contract,@bb/server(197 files; one unrelated timer flake inplugin-update.test.tspasses alone),@bb/server-contract,@bb/sdk,@bb/cli,bb-plugin-simple-notes,@bb/templates.Live on an isolated dev instance (
scripts/bb-dev-app current) against a project and environment on this bb worktree (agit worktree, so.gitis a file): the issue's exact endpointGET /api/v1/environments/<id>/paths?query=ci.yml&limit=5&includeFiles=true&includeDirectories=falsenow returns.github/workflows/ci.ymlfirst (it returned{"paths":[]}on base);&includeHidden=falsehides it again; the full listing has 6,139 entries with zero.turbo,node_modulesor.gitpaths.bb environment paths --no-hidden,bb project paths --no-hiddenandbb file paths --no-hiddenbehave the same. Timing with the report's handler benchmark on~/browser-use(Python repo with a 16k-file.venv),query: "config",limit: 8: base 6-8 ms, #2103 629-1234 ms, this branch 14-17 ms withbrowser_use/config.pyback in the top three; the same repo withrespectGitignore: false(plain capped walk) takes 138-148 ms, which is what the gitignore source avoids.Fixes #2093