`workspaceContains` glob activation events are silently defeated by default `files.exclude` (`**/.git`, `**/.svn`, `**/.hg`), so extensions cannot activate on descendant SCM metadata
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
- VS Code Version: 1.129.0 (commit `125df4672b8a6a34975303c6b0baa124e560a4f7`)
- OS Version: Windows 11 Pro 10.0.26200
- Does this issue occur when all extensions are disabled: N/A (behavior of extension activation itself; reproduced with a minimal probe extension in an isolated `--user-data-dir`/`--extensions-dir`, default settings)
### Summary
Any `workspaceContains` activation event containing `*` or `?` is routed through file search, and that search applies the configured excludes — including the *default* `files.exclude` entries `**/.git`, `**/.svn`, `**/.hg`. As a result, a glob activation event that targets SCM metadata (for example `workspaceContains:**/.svn/wc.db` for a Subversion extension) can never fire, even when the file demonstrably exists in the workspace. Exact (glob-free) patterns use a separate `exists()` branch and are unaffected — but exact patterns cannot express "a working copy in some child directory whose name I don't know".
The practical consequence: an SCM extension for Subversion/Mercurial (or anything keyed off dot-directory metadata) has no `workspaceContains` form that activates when the working copy is a *descendant* of the opened folder. The only workarounds are `onStartupFinished`/`*` (activating in every workspace, which `workspaceContains` exists to avoid) or asking the user to open the working-copy root.
This is silent: nothing in the extension host log indicates the pattern was suppressed by an exclude; the extension simply never activates.
### Minimal repro
Probe extension (plain JS, no build step) — one activation event per extension so the fired event is unambiguous; on activation it writes a marker file:
```json
{
"name": "svnprobe-glob-file",
"publisher": "svnprobe",
"version": "0.0.1",
"engines": { "vscode": "^1.85.0" },
"main": "./extension.js",
"activationEvents": ["workspaceContains:**/.svn/wc.db"],
"capabilities": { "untrustedWorkspaces": { "supported": true } }
}
```
Fixtures (file contents irrelevant; existence matters):
```
wc-root/ .svn/wc.db # working copy at workspace root
parent-ws/ child/.svn/wc.db # working copy in a child dir (the real-world case)
no-svn/ plain.txt # negative control
```
Steps:
1. Install probes into an isolated instance: `code --user-data-dir --extensions-dir --install-extension `.
2. Open each fixture folder; wait past the workspaceContains window; check markers.
### Observed (3 identical runs, isolated instance, default settings)
| activation event | wc-root (`.svn/wc.db` at root) | parent-ws (`child/.svn/wc.db`) | no-svn |
|---|---|---|---|
| `workspaceContains:.svn/wc.db` (exact) | activates | no (correct: path not at root) | no |
| `workspaceContains:**/.svn/wc.db` | **no** | **no** | no |
| `workspaceContains:**/.svn` | no | no | no |
| `workspaceContains:*/.svn` | no | no | no |
| `workspaceContains:*/*/.svn` | no | no | no |
Note the wc-root row: the exact pattern and the `**/` glob describe the *same existing file*, in the same launch, and only the exact one fires.
Public-API confirmation from inside the activated probe (same workspace, `child/.svn/wc.db` present):
```js
await vscode.workspace.findFiles('**/.svn/wc.db', undefined, 10) // [] — default excludes applied
await vscode.workspace.findFiles('**/.svn/wc.db', null, 10) // [ '/.svn/wc.db', '/child/.svn/wc.db' ]
```
### Source analysis (at `125df467`)
1. Pattern routing — anything with `*`/`?` goes to search, not `exists()`:
[`src/vs/workbench/services/extensions/common/workspaceContains.ts#L42-L49`](https://github.com/microsoft/vscode/blob/125df4672b8a6a34975303c6b0baa124e560a4f7/src/vs/workbench/services/extensions/common/workspaceContains.ts#L42-L49)
```ts
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0 || host.forceUsingSearch) {
globPatterns.push(fileNameOrGlob);
} else {
fileNames.push(fileNameOrGlob);
}
```
Exact names use `host.exists(...)` in `_activateIfFileName` (L71-L79), which never consults excludes.
2. The glob branch builds an ordinary file-search query **without** `disregardExcludeSettings`:
[`workspaceContains.ts#L113-L128`](https://github.com/microsoft/vscode/blob/125df4672b8a6a34975303c6b0baa124e560a4f7/src/vs/workbench/services/extensions/common/workspaceContains.ts#L113-L128)
```ts
const query = queryBuilder.file(folders.map(...), {
_reason: 'checkExists',
includePattern: includes,
exists: true
});
```
3. QueryBuilder therefore folds in configured excludes:
[`src/vs/workbench/services/search/common/queryBuilder.ts#L428-L431`](https://github.com/microsoft/vscode/blob/125df4672b8a6a34975303c6b0baa124e560a4f7/src/vs/workbench/services/search/common/queryBuilder.ts#L428-L431)
```ts
private getExcludesForFolder(folderConfig: ISearchConfiguration, options: ICommonQueryBuilderOptions): glob.IExpression | undefined {
return options.disregardExcludeSettings ?
undefined :
getExcludes(folderConfig, !options.disregardSearchExcludeSettings);
}
```
4. And `files.exclude` *defaults* to excluding SCM metadata everywhere:
[`src/vs/workbench/contrib/files/browser/files.contribution.ts#L153-L158`](https://github.com/microsoft/vscode/blob/125df4672b8a6a34975303c6b0baa124e560a4f7/src/vs/workbench/contrib/files/browser/files.contribution.ts#L153-L158)
```ts
'default': {
...{ '**/.git': true, '**/.svn': true, '**/.hg': true, '**/.DS_Store': true, '**/Thumbs.db': true },
```
Hidden-file handling is not the blocker: the ripgrep provider passes `--hidden` ([`ripgrepFileSearch.ts#L31`](https://github.com/microsoft/vscode/blob/125df4672b8a6a34975303c6b0baa124e560a4f7/src/vs/workbench/services/search/node/ripgrepFileSearch.ts#L31)).
### Expected
`workspaceContains` is documented as activating "whenever a folder is opened that contains at least one file that matches [the] glob pattern". Activation is a statement about workspace *content*; `files.exclude` is a *display/search* filter. A user hiding `.svn` from the Explorer does not intend to disable their Subversion extension — and here it is not even a user choice, it is the product default, so the manifest surface (`workspaceContains:**/.svn/...`) is dead on arrival for every user.
### Suggested fix direction
Have `checkGlobFileExists` build its query with `disregardExcludeSettings: true` (and `disregardIgnoreFiles: true`), matching the semantics of the exact-path branch, which already ignores excludes.
If that is considered too costly — excludes were deliberately applied to these searches for performance in #34711 (avoiding `node_modules`/`.git` walks) — a bounded middle ground is the one already suggested in #34711's discussion: only disregard an exclude when the activation glob explicitly names that directory (e.g. a pattern containing a literal `.svn` segment suppresses the `**/.svn` exclude for this query only). That keeps `**/*.ts`-style patterns cheap while making `**/.svn/wc.db` mean what it says. At minimum, the current behavior deserves a documentation note and an extension-host log line when a `workspaceContains` search returns empty solely due to excludes.
### Related issues
- #34711 — `workspaceContains` starts a search over full workspace, including `.git/`, `node_modules/` (closed 2018; the perf motivation for applying excludes, and the origin of the "how do you then activate on `.git/HEAD`?" question this issue is the answer to)
- #323964 — `.gitignore` will prevent an extension from launching (open; same mechanism via `search.useIgnoreFiles` instead of default `files.exclude`)
- #2739 — `activationEvents.workspaceContains` doesn't fire for directory (open; independent second blocker: directory-shaped patterns like `**/.svn` never match because file search matches files — reproduced here: `findFiles('**/.svn', null)` is also empty)
- #242245 — newly installed extensions not activating on `workspaceContains` trigger (adjacent reliability report)
Contributor guide
Research direction
Start with src/vs/workbench/services/extensions/common/workspaceContains.ts and trace its glob query into src/vs/workbench/services/search/common/queryBuilder.ts; compare that path with the exact-name branch. Reproduce using the minimal probe and workspace fixtures described in the issue. Done means a workspaceContains glob can detect the matching SCM metadata despite default excludes, or the chosen behavior is documented and surfaced in the extension-host logs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100