You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The extension currently has two independent, inconsistently-implemented mechanisms for deciding "what is the Bazel workspace" for a given piece of UI: one resolved per open file, one resolved once per VS Code folder. Several open issues and one open PR (#625) are all symptoms of these two mechanisms disagreeing, or of auto-detection being structurally unable to find the right root at all. This issue proposes:
Adopting a single new setting, bazel.workspacePath (originally proposed in Feat/manual workspace path #625), as the supported way to pin one Bazel workspace root per VS Code folder whenever auto-detection is ambiguous or impossible.
Explicitly scoping that setting to solve the root-resolution problem only, and explicitly not trying to make one VS Code folder host several independent, simultaneously-active Bazel roots — that case is delegated to VS Code's native multi-root workspaces, which the setting's resource scope already supports per folder.
Sequencing the remaining implementation work needed to make that guarantee hold for every feature in the extension.
BazelWorkspaceInfo (src/bazel/bazel_workspace_info.ts) is the shared type every feature uses to learn "where is the Bazel workspace root," but it is populated in two structurally different ways:
The VS Code workspace folder root itself, same upward walk
Once per folder (effectively)
Both funnel through the same getBazelWorkspaceFolder() in bazel_utils.ts, and that function can only search upward. That single fact explains most of the bug reports below:
If the file (or the VS Code folder root) is at or below the true Bazel root, the upward walk finds it (possibly the wrong ancestor if there's a nested MODULE.bazel in between — see Scenario S5).
If the true Bazel root is a descendant of the VS Code folder root, no upward walk from the folder root can ever find it. This is not a bug to patch, it is an structural impossibility of the current algorithm (Scenario S4).
Feature classification: which mechanism does each feature use
Static features are, by construction, single-Bazel-root-per-VS-Code-folder. There is no per-file variant of "Bazel: Build Target" — it always resolves against the folder that owns the active editor (or the picked folder in a multi-root window), never against "the nearest root to whichever file is focused." No config option changes this without a much larger rewrite of every one of these commands.
Dynamic features can, in principle, support several independent Bazel roots coexisting under one VS Code folder — each open file resolves its own nearest root. This is a real capability some users rely on today (unrelated sibling checkouts opened together), and it's important not to regress it while fixing the ambiguous-nesting case (S5 below).
Note on the completion provider (post-#638): it's no longer purely dynamic. Lookup (provideCompletionItems) still resolves the workspace per file via fromDocument (dynamic), but its target cache (targetsMap) is populated per VS Code folder via fromWorkspaceFolder/getAll() (static) and keyed by that folder-resolved bazelWorkspacePath. In Scenario S5, a file physically inside a nested override'd module resolves (dynamically, nearest-wins) to the nested module's own path, which never matches the cache key populated for the outer root — so completions silently come back empty for files in that subtree today. This is a fresh, concrete instance of exactly the bug class this design is about; see "Still needed," item 7.
Scenarios identified
#
Layout
Auto-detection today
Notes
S1
VS Code folder root == Bazel root
✅ correct
Baseline case, no config needed.
S2
N VS Code folders (multi-root workspace), each == its own Bazel root
✅ correct
bazel.workspacePath is scope: "resource", so each folder can also be configured independently if needed.
S3
Bazel root is an ancestor of the VS Code folder (folder opened in a subdirectory of the workspace)
#608 (merged) fixed 4 call sites that used the VS Code folder path instead of the discovered Bazel root as the query/spawn cwd. Static features scope their queries to the subfolder for performance.
S4
VS Code folder is an ancestor of the Bazel root (root opened above a nested MODULE.bazel)
❌ impossible for static features; dynamic features only work if the specific open file happens to already be under the nested root
Upward search from the folder root structurally cannot descend into it. This is issue #472's "vscode-workspace containing bazel_root/MODULE.bazel" case, and the one Karnabanu tested against #625.
S5
Multiple MODULE.bazel/WORKSPACE files nested under one VS Code folder, where the nearer one is not the one that should be used (typically a local_path_override'd internal dependency whose own MODULE.bazel lacks the root's overrides)
❌ "nearest wins" picks the wrong root
This is #621 and #472's core complaint. Correct behavior differs by file: files under the override'd sub-module still need queries to run with the root's overrides in scope.
S6
A directory within the workspace tree is structurally a separate Bazel repository (local_repository/bzlmod module override) that users nonetheless expect to browse/build/navigate as if it were part of the main workspace
⚠️ partially broken independent of root-resolution
This is #416's territory — see below. It is entangled with S5 (same directory layout) but is a different axis of the problem: which cwd to run Bazel from vs. which label prefix (// vs @repo//) a file's targets live under.
#416 ("Handle external dependencies") observes that the tree explorer, coverage, quickpick, and codelens all hard-code //...-rooted queries (//...:*, kind(rule, //pkg:all), etc.), so packages that live under an external repository (@repo//...) never show up, even though the repository is physically nested inside the workspace directory tree via local_repository/local_path_override.
This is the same directory layout as Scenario S5, but a different bug: even with a perfectly resolved bazelWorkspacePath and a correct cwd, bazel query //internal_dependency/...:* from the root will not return the override'd module's targets, because Bazel does not traverse repository boundaries with an unprefixed //... pattern — the correct query is @internal_dependency//...:*. No amount of cwd/root pinning fixes this; it requires the query-construction and label-formatting logic (package labels, tree grouping, codelens, coverage path mapping) to become repository-aware, which is exactly what #416 already scopes out as follow-up work.
Conclusion: #416 should stay a separate issue, but this design must not implement bazel.workspacePath in a way that forecloses solving #416 later — e.g. it must not assume every file under the pinned root belongs to the // label namespace of that root (see "Does this let dynamic features simplify?" below).
Implements the proposed bazel.workspacePath setting; fixes root resolution (S4/S5) but not the static-feature relative-path math when the root diverges from the folder.
Supplemental fix: adds a containment-aware relative-path helper, fixes the //../... invalid-query bug in the tree view (S4), and fixes editor→tree sync to use bazelWorkspacePath instead of the VS Code folder path.
Extension adds MODULE.bazel and MODULE.bazel.lock in local repositories
open
Likely a symptom of the extension (buildifier/bzlmod tooling) mis-treating a nested local_repository directory as its own root boundary — same root cause class as S5/S6.
fix: silence workspace error in non-Bazel repos (merged 2026-08-19)
merged
Not a root-resolution fix, but adds BazelWorkspaceInfo.getAll() and makes the completion provider iterate/cache per VS Code folder instead of prompting via fromWorkspaceFolders(). Reinforces the one-root-per-folder model this design assumes, and exposes a new S5 gap in the completion provider (see feature table note and "Still needed" #7). Closes #446.
Adjacent: graceful-degradation when getBazelWorkspaceFolder legitimately finds nothing, not a divergence case. Listed for completeness since it shares BazelWorkspaceInfo machinery.
Adjacent but distinct axis (query/label scoping within a single, correctly-resolved root — not covered by this design decision): #598, #480 (closed), #618 (closed), #459 (closed), #174.
Design decision
Proposal
The design decision being made here is to make explicit, intentional, and the only supported setup an assumption the codebase has always implicitly leaned on but never stated or enforced: there is exactly one Bazel workspace root per VS Code workspace folder. Every scenario in this document where the extension misbehaves is a case where that assumption silently didn't hold (S4, S5) or held only by luck (S1, S3). Adopting it explicitly means the extension no longer tries to infer a single correct answer in cases where none can be inferred — it either finds one unambiguously, or it requires the user to state it.
Adding bazel.workspacePath (resource-scoped string, "" default falling back to auto-detect) is the logical consequence of that decision: it's simply the mechanism by which a user states the root when the extension can't determine it unambiguously on its own (S4, S5). When set, it deterministically pins exactly one Bazel workspace root for that VS Code folder, for both static and dynamic features.
Argumentation for why multi-root-in-one-folder is out of scope by design
Making a single VS Code folder host N independently-selectable Bazel roots simultaneously is a separate problem from root divergence but would make correct handling of the root divergence even harder. This design document does not try to support this because:
Every static feature (tree view, build/test/run, tasks, command variables) is one-root-per-folder by construction; making it multi-root would mean redesigning those features to disambiguate "which root" per invocation (new UI, new commands), not a config-shape change.
VS Code already has a first-class primitive for "several roots, each with its own settings": multi-root workspaces. bazel.workspacePath's scope: "resource" means each folder in a multi-root workspace can already carry its own value (or none, falling back to auto-detect). A user who genuinely has two unrelated, simultaneously-needed Bazel roots inside what is today one VS Code folder can split it into two folders in a .code-workspace file and get full, correct support for both, today.
Bazel itself is designed around monorepos, not federations of unrelated workspaces sharing a directory. When a directory tree contains more than one MODULE.bazel/WORKSPACE, the far more common real-world case (and the one every motivating issue here — getBazelWorkspaceFolder for multiple MODULE.bazel files #621, bazel 8 + bzlmod and child modules resolution #472, Handle external dependencies #416 — actually describes) is not "two independent Bazel projects that happen to be nested," it's one root workspace that deliberately stitches the others in via local_path_override/local_repository. That's a single logical workspace with one correct root, not a genuine multi-root scenario. So the set of users who need true, simultaneous multi-root support (independent projects, no stitching root, both needed live at once) is smaller than it might first appear — most "multiple MODULE.bazel files under one folder" reports are actually S5, which pinning already solves correctly, not a case that needs multi-root support at all.
So: multi-root-in-one-folder is intentionally not handled by pinning; it's handled by asking users to use multi-root VS Code workspaces, which the extension already supports per-folder. Given point 3, we expect this to affect very few real users — most people who think they need it actually have a stitched monorepo (S5) and just need to point bazel.workspacePath at the stitching root. This should be stated explicitly in the setting's documentation (markdownDescription in package.json) so it isn't rediscovered as a bug report later.
Explicitly out of scope for this design decision (separate follow-ups)
Extension adds MODULE.bazel and MODULE.bazel.lock in local repositories #383 — likely resolves itself once S5/S6 cwd resolution is correct (the extension should stop treating a nested override'd module as an independent root to run bzlmod tooling against), but should be re-tested against the fix rather than folded into this PR.
This is a (AI generated) meta-issue, collecting related work and documenting the resulting design decision.
Based on:
bazel-contrib/vscode-bazel@84484e6(master, 2026-08-19) — all file:line references below are against this revision.Related: #625, #621, #472, #428, #383, #416, #608, #607, #638, #446
TL;DR
The extension currently has two independent, inconsistently-implemented mechanisms for deciding "what is the Bazel workspace" for a given piece of UI: one resolved per open file, one resolved once per VS Code folder. Several open issues and one open PR (#625) are all symptoms of these two mechanisms disagreeing, or of auto-detection being structurally unable to find the right root at all. This issue proposes:
bazel.workspacePath(originally proposed in Feat/manual workspace path #625), as the supported way to pin one Bazel workspace root per VS Code folder whenever auto-detection is ambiguous or impossible.resourcescope already supports per folder.Problem statement
Two anchoring mechanisms
BazelWorkspaceInfo(src/bazel/bazel_workspace_info.ts) is the shared type every feature uses to learn "where is the Bazel workspace root," but it is populated in two structurally different ways:BazelWorkspaceInfo.fromDocument(document)→getBazelWorkspaceFolder(uri.fsPath)MODULE.bazel/REPO.bazel/WORKSPACE.bazel/WORKSPACEfile is foundBazelWorkspaceInfo.fromWorkspaceFolder(s)→getBazelWorkspaceFolder(workspaceFolder.uri.fsPath)Both funnel through the same
getBazelWorkspaceFolder()in bazel_utils.ts, and that function can only search upward. That single fact explains most of the bug reports below:MODULE.bazelin between — see Scenario S5).Feature classification: which mechanism does each feature use
fromWorkspaceFolders())tasks.jsonbazel task provider)${command:...}task/launch variablesStatic features are, by construction, single-Bazel-root-per-VS-Code-folder. There is no per-file variant of "Bazel: Build Target" — it always resolves against the folder that owns the active editor (or the picked folder in a multi-root window), never against "the nearest root to whichever file is focused." No config option changes this without a much larger rewrite of every one of these commands.
Dynamic features can, in principle, support several independent Bazel roots coexisting under one VS Code folder — each open file resolves its own nearest root. This is a real capability some users rely on today (unrelated sibling checkouts opened together), and it's important not to regress it while fixing the ambiguous-nesting case (S5 below).
Note on the completion provider (post-#638): it's no longer purely dynamic. Lookup (
provideCompletionItems) still resolves the workspace per file viafromDocument(dynamic), but its target cache (targetsMap) is populated per VS Code folder viafromWorkspaceFolder/getAll()(static) and keyed by that folder-resolvedbazelWorkspacePath. In Scenario S5, a file physically inside a nested override'd module resolves (dynamically, nearest-wins) to the nested module's own path, which never matches the cache key populated for the outer root — so completions silently come back empty for files in that subtree today. This is a fresh, concrete instance of exactly the bug class this design is about; see "Still needed," item 7.Scenarios identified
bazel.workspacePathisscope: "resource", so each folder can also be configured independently if needed.MODULE.bazel)MODULE.bazel/WORKSPACEfiles nested under one VS Code folder, where the nearer one is not the one that should be used (typically alocal_path_override'd internal dependency whose ownMODULE.bazellacks the root's overrides)local_repository/bzlmod module override) that users nonetheless expect to browse/build/navigate as if it were part of the main workspace//vs@repo//) a file's targets live under.#416 and its relationship to this problem
#416 ("Handle external dependencies") observes that the tree explorer, coverage, quickpick, and codelens all hard-code
//...-rooted queries (//...:*,kind(rule, //pkg:all), etc.), so packages that live under an external repository (@repo//...) never show up, even though the repository is physically nested inside the workspace directory tree vialocal_repository/local_path_override.This is the same directory layout as Scenario S5, but a different bug: even with a perfectly resolved
bazelWorkspacePathand a correct cwd,bazel query //internal_dependency/...:*from the root will not return the override'd module's targets, because Bazel does not traverse repository boundaries with an unprefixed//...pattern — the correct query is@internal_dependency//...:*. No amount of cwd/root pinning fixes this; it requires the query-construction and label-formatting logic (package labels, tree grouping, codelens, coverage path mapping) to become repository-aware, which is exactly what #416 already scopes out as follow-up work.Conclusion: #416 should stay a separate issue, but this design must not implement
bazel.workspacePathin a way that forecloses solving #416 later — e.g. it must not assume every file under the pinned root belongs to the//label namespace of that root (see "Does this let dynamic features simplify?" below).Related GitHub issues and PRs
feat(config): add bazel.workspacePathbazel.workspacePathsetting; fixes root resolution (S4/S5) but not the static-feature relative-path math when the root diverges from the folder.fix: handle Bazel roots nested under workspace folders//../...invalid-query bug in the tree view (S4), and fixes editor→tree sync to usebazelWorkspacePathinstead of the VS Code folder path.getBazelWorkspaceFolder for multiple MODULE.bazel filesbazel 8 + bzlmod and child modules resolutionExtension cannot find bazel root without a WORKSPACE fileMODULE.bazelis already searched.Extension adds MODULE.bazel and MODULE.bazel.lock in local repositorieslocal_repositorydirectory as its own root boundary — same root cause class as S5/S6.Handle external dependenciesBazelQuery uses VS Code workspace folder path instead of Bazel workspace root in multi-root workspacesfix: use bazelWorkspacePath instead of workspaceFolder.uri.fsPath(merged)fix: silence workspace error in non-Bazel repos(merged 2026-08-19)BazelWorkspaceInfo.getAll()and makes the completion provider iterate/cache per VS Code folder instead of prompting viafromWorkspaceFolders(). Reinforces the one-root-per-folder model this design assumes, and exposes a new S5 gap in the completion provider (see feature table note and "Still needed" #7). Closes #446."Failed to find a Bazel Workspace file" on non bazel repos that include BUILD filesgetBazelWorkspaceFolderlegitimately finds nothing, not a divergence case. Listed for completeness since it sharesBazelWorkspaceInfomachinery.Adjacent but distinct axis (query/label scoping within a single, correctly-resolved root — not covered by this design decision): #598, #480 (closed), #618 (closed), #459 (closed), #174.
Design decision
Proposal
The design decision being made here is to make explicit, intentional, and the only supported setup an assumption the codebase has always implicitly leaned on but never stated or enforced: there is exactly one Bazel workspace root per VS Code workspace folder. Every scenario in this document where the extension misbehaves is a case where that assumption silently didn't hold (S4, S5) or held only by luck (S1, S3). Adopting it explicitly means the extension no longer tries to infer a single correct answer in cases where none can be inferred — it either finds one unambiguously, or it requires the user to state it.
Adding
bazel.workspacePath(resource-scoped string,""default falling back to auto-detect) is the logical consequence of that decision: it's simply the mechanism by which a user states the root when the extension can't determine it unambiguously on its own (S4, S5). When set, it deterministically pins exactly one Bazel workspace root for that VS Code folder, for both static and dynamic features.Argumentation for why multi-root-in-one-folder is out of scope by design
Making a single VS Code folder host N independently-selectable Bazel roots simultaneously is a separate problem from root divergence but would make correct handling of the root divergence even harder. This design document does not try to support this because:
bazel.workspacePath'sscope: "resource"means each folder in a multi-root workspace can already carry its own value (or none, falling back to auto-detect). A user who genuinely has two unrelated, simultaneously-needed Bazel roots inside what is today one VS Code folder can split it into two folders in a.code-workspacefile and get full, correct support for both, today.MODULE.bazel/WORKSPACE, the far more common real-world case (and the one every motivating issue here — getBazelWorkspaceFolder for multiple MODULE.bazel files #621, bazel 8 + bzlmod and child modules resolution #472, Handle external dependencies #416 — actually describes) is not "two independent Bazel projects that happen to be nested," it's one root workspace that deliberately stitches the others in vialocal_path_override/local_repository. That's a single logical workspace with one correct root, not a genuine multi-root scenario. So the set of users who need true, simultaneous multi-root support (independent projects, no stitching root, both needed live at once) is smaller than it might first appear — most "multipleMODULE.bazelfiles under one folder" reports are actually S5, which pinning already solves correctly, not a case that needs multi-root support at all.So: multi-root-in-one-folder is intentionally not handled by pinning; it's handled by asking users to use multi-root VS Code workspaces, which the extension already supports per-folder. Given point 3, we expect this to affect very few real users — most people who think they need it actually have a stitched monorepo (S5) and just need to point
bazel.workspacePathat the stitching root. This should be stated explicitly in the setting's documentation (markdownDescriptioninpackage.json) so it isn't rediscovered as a bug report later.Explicitly out of scope for this design decision (separate follow-ups)
Bazel targetsdoes not honorbazel.commandLine.queryExpressionfor workspace root targets #480 / Bazel Targets tree lists extraneous targets #618 / Add an option to limit pick list queries to the nearest package #459 (query-expression scoping within an already-correct root) — unrelated axis, no action needed here.