Summary
The LWC extension causes a continuous file-watcher load in large SFDX projects. Two defects combine.
writeSfdxJsconfig() writes jsconfig.json on every call. The function does not compare the new content with the old content.
- The LWC client watches
**/ with ignoreCreateEvents: false. The Apex and Lightning extensions use ignoreCreateEvents: true for the same purpose.
The extension writes into the workspace. The file watcher sees the write. The watcher event starts a new index operation. The index operation regenerates the LWC typings. The regeneration creates more watcher events.
All CPU figures below use the percent-of-one-core convention. The top command uses the same convention. My machine has 14 logical CPUs, thus the maximum is 1400%. The Windows Task Manager uses a different convention. It divides by the total capacity. Thus it shows a smaller number for the same load.
Defect 1: unconditional write
packages/salesforcedx-lightning-lsp-common/src/baseContext.ts
383: const existingConfigContent = await this.fileSystemAccessor.getFileContent(jsconfigPath);
// ...merge...
426: jsconfigContent = JSON.stringify(mergedConfig, null, 4);
452: await this.fileSystemAccessor.updateFileContent(jsconfigPath, jsconfigContent);
Line 383 reads the old content. Line 452 writes the new content. The code does not compare the two values.
updateForceIgnoreFile() in the same file uses the correct pattern. writeSfdxJsconfig() calls this function at line 466.
115: const originalContent = forceignoreContent;
// ...append patterns...
136: if (forceignoreContent === originalContent) {
137: return;
138: }
Defect 2: watcher flag
Three packages create the same watcher. The LWC package is different from the other two.
// packages/salesforcedx-vscode-apex/src/languageServer.ts:234
createFileSystemWatcher('**/', true, true, false), // only events for folder deletions
// packages/salesforcedx-vscode-lightning/src/index.ts:156
createFileSystemWatcher('**/', true, true, false),
// packages/salesforcedx-vscode-lwc/src/languageClient/clientOptions.ts:33
// need to watch for directory deletions as no events are created for contents or deleted directories
createFileSystemWatcher('**/', false, true, false) // ignoreCreateEvents = false
The signature is (pattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents). The comment says that the watcher observes deletions. The code also observes the creation of every directory in the workspace. https://code.visualstudio.com/api/references/vscode-api#workspace
Steps To Reproduce:
-
Open a large SFDX project in VS Code. My project has 191 LWC bundles and 496 Apex classes.
-
Make sure that files.watcherExclude does not exclude .sfdx. This is the default condition.
-
Do not use the editor for some minutes.
-
Run the PowerShell script below. The script samples the CPU time for 8 seconds. It reports both conventions.
$cores = [Environment]::ProcessorCount
$ids = (Get-CimInstance Win32_Process -Filter "Name='Code.exe'").ProcessId
$t1 = @{}
foreach ($i in $ids) {
$p = Get-CimInstance Win32_Process -Filter "ProcessId=$i" -EA SilentlyContinue
if ($p) { $t1[$i] = ($p.KernelModeTime + $p.UserModeTime) / 1e7 }
}
$sw = [Diagnostics.Stopwatch]::StartNew(); Start-Sleep -Seconds 8; $sw.Stop()
$el = $sw.Elapsed.TotalSeconds
foreach ($i in $ids) {
$p = Get-CimInstance Win32_Process -Filter "ProcessId=$i" -EA SilentlyContinue
if ($p -and $t1.ContainsKey($i)) {
$d = (($p.KernelModeTime + $p.UserModeTime) / 1e7) - $t1[$i]
if ($d -gt 0.05) {
[pscustomobject]@{
PID = $i
CPUsec = [math]::Round($d, 2)
PctOneCore = [math]::Round($d / $el * 100, 1)
PctMachine = [math]::Round($d / ($el * $cores) * 100, 1)
}
}
}
}
-
Find the process with the highest CPUsec value.
-
Run (Get-CimInstance Win32_Process -Filter "ProcessId=<PID>").CommandLine for that process. The command line contains --utility-sub-type=node.mojom.NodeService. This process runs the file watcher.
-
Look at the modification times of force-app/main/default/lwc/jsconfig.json.
-
Look at the modification times of the files in .sfdx/typings/lwc/.
Expected result
The file watcher uses almost no CPU while the editor is idle.
The extension writes jsconfig.json only when the content changes.
Actual result
The file-watcher process keeps one core to two cores busy while the editor is idle. The process accumulated 4.9 hours of CPU time in one session.
Two measurements from the same session follow. The editor was idle for both.
| Sample |
Interval |
CPU time used |
Percent of one core |
Percent of machine |
| 1 |
6.00 s |
7.6 s |
126.6% |
9.0% |
| 2 |
8.01 s |
16.75 s |
209.2% |
14.9% |
The machine has 14 logical CPUs. The process uses more than one thread, thus it uses more than one core.
The extension regenerates the 5,166 .d.ts files in .sfdx/typings/lwc/. These files and jsconfig.json get new modification times again and again. The content of jsconfig.json does not change.
The extension host becomes unstable under this load. My renderer.log file has 915 entries that report an unresponsive extension host. The same log shows the exits of 14 different extension-host process IDs.
The extension host starts git.exe child processes. When the extension host exits, the child processes become orphans. I found 10 orphaned git.exe processes. They stayed blocked for two days. Because of these processes, the built-in Git view and the diff view do not work correctly. A git status command in a terminal stays fast at 265 ms to 421 ms.
Additional information
Suggested fix for defect 1
Compare the content before the write in writeSfdxJsconfig():
if (jsconfigContent === existingConfigContent) {
continue;
}
await this.fileSystemAccessor.updateFileContent(jsconfigPath, jsconfigContent);
Suggested fix for defect 2
Set ignoreCreateEvents to true in clientOptions.ts at line 33. This value agrees with the comment. It also agrees with the Apex and Lightning packages.
createFileSystemWatcher('**/', true, true, false)
Workaround
Add files.watcherExclude for **/.sfdx/** to the workspace settings. This setting reduces the load, but it does not remove the cause.
Note on .forceignore
writeSfdxJsconfig() also fore appends two patterns to .forceignore. The patterns are **/jsconfig.json and **/.eslintrc.json. This write has the correct guard. I report it because the extension changes a file that is under source control. Maybe, the extension should be able to distinguish between developer tooling actual sources, so it does not tries to parse and deploy local configs, even if these are not even in a proper source folder or have associated *-meta.xml and related *.ts, *.js, or any other LWC specific source files with them. Check: jsconfig.json
Project Info:
Project Info
Generated: 2026-08-07T05:55:07.771Z
Metadata
sourceApiVersion: 67.0
packageDirectories: 1
namespace:
Types
| Type |
Components |
Files |
Size (KB) |
| ApexClass |
496 |
1488 |
6364 |
| ApexComponent |
12 |
36 |
28 |
| ApexEmailNotifications |
1 |
1 |
0 |
| ApexPage |
39 |
117 |
169 |
| ApexTrigger |
35 |
105 |
30 |
| ApprovalProcess |
2 |
2 |
5 |
| AssignmentRules |
2 |
2 |
318 |
| Audience |
32 |
32 |
51 |
| AuraDefinitionBundle |
29 |
180 |
255 |
| AutoResponseRules |
2 |
2 |
2 |
| Bot |
3 |
9 |
56 |
| CampaignInfluenceModel |
1 |
1 |
0 |
| Certificate |
6 |
18 |
22 |
| CleanDataService |
3 |
3 |
57 |
| Community |
1 |
1 |
0 |
| ConnectedApp |
6 |
6 |
10 |
| ContentAsset |
5031 |
15093 |
292845 |
| CspTrustedSite |
26 |
26 |
18 |
| CustomApplication |
28 |
28 |
1440 |
| CustomFeedFilter |
3 |
3 |
1 |
| CustomField |
1 |
1 |
1 |
| CustomHelpMenuSection |
1 |
1 |
1 |
| CustomIndex |
3 |
3 |
0 |
| CustomLabels |
1 |
1 |
343 |
| CustomMetadata |
1811 |
1811 |
2084 |
| CustomNotificationType |
1 |
1 |
0 |
| CustomObject |
411 |
6748 |
8118 |
| CustomObjectTranslation |
1806 |
18988 |
11850 |
| CustomPermission |
16 |
16 |
4 |
| CustomSite |
9 |
9 |
17 |
| CustomTab |
32 |
32 |
7 |
| Dashboard |
2 |
2 |
7 |
| DashboardFolder |
1 |
1 |
1 |
| DataCategoryGroup |
3 |
3 |
6 |
| DigitalExperience |
17 |
51 |
23 |
| DigitalExperienceBundle |
1 |
1 |
1 |
| DigitalExperienceConfig |
1 |
1 |
0 |
| Document |
23 |
69 |
3117 |
| DocumentFolder |
1 |
1 |
0 |
| DuplicateRule |
11 |
11 |
20 |
| EmailFolder |
5 |
5 |
13 |
| EmailServicesFunction |
2 |
2 |
2 |
| EmailTemplate |
122 |
366 |
745 |
| EmbeddedServiceConfig |
1 |
1 |
3 |
| EscalationRules |
1 |
1 |
0 |
| ExperienceBundle |
4 |
449 |
813 |
| ExternalClientApplication |
2 |
2 |
1 |
| ExternalCredential |
2 |
2 |
3 |
| ExternalDataSource |
1 |
1 |
1 |
| ExtlClntAppConfigurablePolicies |
2 |
2 |
1 |
| ExtlClntAppGlobalOauthSettings |
2 |
2 |
3 |
| ExtlClntAppOauthConfigurablePolicies |
2 |
2 |
2 |
| ExtlClntAppOauthSecuritySettings |
2 |
2 |
1 |
| ExtlClntAppOauthSettings |
2 |
2 |
1 |
| FlexiPage |
75 |
75 |
1632 |
| Flow |
151 |
151 |
2682 |
| FlowDefinition |
93 |
93 |
16 |
| ForecastingType |
20 |
20 |
9 |
| GenAiFunction |
10 |
40 |
34 |
| GenAiPlannerBundle |
2 |
4 |
3 |
| GenAiPlugin |
3 |
3 |
12 |
| GenAiPromptTemplate |
1 |
1 |
18 |
| GlobalValueSet |
32 |
32 |
288 |
| GlobalValueSetTranslation |
142 |
142 |
1443 |
| Group |
282 |
282 |
52 |
| HomePageLayout |
1 |
1 |
1 |
| IframeWhiteListUrlSettings |
1 |
1 |
0 |
| InstalledPackage |
14 |
14 |
4 |
| Layout |
402 |
402 |
1847 |
| LeadConvertSettings |
1 |
1 |
5 |
| Letterhead |
1 |
1 |
1 |
| LightningComponentBundle |
191 |
899 |
2973 |
| LightningMessageChannel |
3 |
3 |
1 |
| ListView |
1 |
1 |
1 |
| ManagedContentType |
3 |
3 |
5 |
| ManagedTopics |
5 |
5 |
1 |
| MatchingRules |
5 |
5 |
9 |
| MessagingChannel |
1 |
1 |
4 |
| MilestoneType |
8 |
8 |
2 |
| NamedCredential |
6 |
6 |
4 |
| NavigationMenu |
15 |
15 |
24 |
| Network |
4 |
4 |
13 |
| NetworkBranding |
5 |
15 |
54 |
| NotificationTypeConfig |
1 |
1 |
22 |
| PathAssistant |
11 |
11 |
11 |
| PermissionSet |
86 |
86 |
1585 |
| PermissionSetGroup |
3 |
3 |
2 |
| PresenceUserConfig |
1 |
1 |
1 |
| Profile |
27 |
27 |
23939 |
| ProfilePasswordPolicy |
17 |
17 |
11 |
| ProfileSessionSetting |
16 |
16 |
7 |
| Prompt |
6 |
6 |
11 |
| Queue |
198 |
198 |
132 |
| QueueRoutingConfig |
3 |
3 |
1 |
| QuickAction |
120 |
120 |
151 |
| RemoteSiteSetting |
21 |
21 |
6 |
| Report |
22 |
22 |
53 |
| ReportFolder |
6 |
6 |
3 |
| ReportType |
90 |
90 |
2050 |
| Role |
2383 |
2383 |
1153 |
| SamlSsoConfig |
3 |
3 |
8 |
| Settings |
125 |
125 |
1739 |
| SharingRules |
189 |
189 |
49 |
| SharingSet |
1 |
1 |
1 |
| SiteDotCom |
4 |
12 |
4706 |
| StandardValueSet |
38 |
38 |
99 |
| StandardValueSetTranslation |
172 |
172 |
858 |
| StaticResource |
45 |
2627 |
51491 |
| Territory2 |
127 |
127 |
68 |
| Territory2Model |
2 |
2 |
0 |
| Territory2Rule |
83 |
83 |
45 |
| Territory2Type |
6 |
6 |
1 |
| TimeSheetTemplate |
1 |
1 |
1 |
| TopicsForObjects |
146 |
146 |
32 |
| Translations |
21 |
21 |
17879 |
| UserProvisioningConfig |
1 |
1 |
0 |
| WebStoreTemplate |
4 |
4 |
5 |
| Workflow |
7 |
7 |
9 |
Org
| Key |
Value |
| Org type |
production |
| Source tracking |
false |
| SourceMember count |
N/A |
Settings
| Setting |
Value |
| salesforcedx-vscode-metadata.showSuccessNotification |
true |
| salesforcedx-vscode-metadata.sourceTracking.pollingIntervalSeconds |
0 |
| salesforcedx-vscode-core.push-or-deploy-on-save.enabled |
false |
| salesforcedx-vscode-core.push-or-deploy-on-save.ignoreConflictsOnPush |
false |
| salesforcedx-vscode-core.detectConflictsForDeployAndRetrieve |
false |
| salesforcedx-vscode-core.clearOutputTab |
false |
| salesforcedx-vscode-core.show-cli-success-msg |
true |
| salesforcedx-vscode-core.telemetry.enabled |
false |
| salesforcedx-vscode-core.enable-sobject-refresh-on-startup |
true |
| salesforcedx-vscode-core.telemetry-tag |
|
| salesforcedx-vscode-salesforcedx.enableLocalTraces |
false |
| salesforcedx-vscode-salesforcedx.enableConsoleTraces |
false |
| salesforcedx-vscode-salesforcedx.enableFileTraces |
false |
| salesforcedx-vscode-apex.java.home |
C:\Program Files\Java\jdk-25.0.2 |
Environment
| Key |
Value |
| Salesforce CLI |
@salesforce/cli/2.142.7 win32-x64 node-v24.14.1 |
| Java |
java 25.0.2 2026-01-20 LTS |
| Editor |
Visual Studio Code |
| VS Code |
1.132.0 |
| Node |
v24.18.0 |
| OS |
Windows_NT 10.0.26200 |
Extensions (Salesforce)
| Extension |
Version |
| salesforce.apex-language-server-extension |
0.8.0 |
| salesforce.mule-dx-api-component |
4.21.0 |
| salesforce.mule-dx-apikit-component |
1.4.0 |
| salesforce.mule-dx-data-weave-client |
2.13.0 |
| salesforce.mule-dx-dependencies |
1.7.1 |
| salesforce.mule-dx-extension-pack |
1.24.1 |
| salesforce.mule-dx-mule-dev-component |
3.31.4 |
| salesforce.mule-dx-munit-component |
1.3.13 |
| salesforce.mule-dx-runtime |
1.2.0 |
| salesforce.mule-dx-vscode |
5.18.3 |
| salesforce.salesforce-vscode-slds |
2.0.12 |
| salesforce.salesforcedx-metadata-visualizer-vscode |
1.2.0 |
| salesforce.salesforcedx-vscode |
67.7.1 |
| salesforce.salesforcedx-vscode-apex |
67.7.1 |
| salesforce.salesforcedx-vscode-apex-log |
67.7.1 |
| salesforce.salesforcedx-vscode-apex-replay-debugger |
67.7.1 |
| salesforce.salesforcedx-vscode-apex-testing |
67.7.1 |
| salesforce.salesforcedx-vscode-core |
67.7.1 |
| salesforce.salesforcedx-vscode-expanded |
67.7.1 |
| salesforce.salesforcedx-vscode-lightning |
67.7.1 |
| salesforce.salesforcedx-vscode-lwc |
67.7.1 |
| salesforce.salesforcedx-vscode-metadata |
67.7.1 |
| salesforce.salesforcedx-vscode-org |
67.7.1 |
| salesforce.salesforcedx-vscode-org-browser |
67.7.1 |
| salesforce.salesforcedx-vscode-services |
67.7.1 |
| salesforce.salesforcedx-vscode-soql |
67.7.1 |
| salesforce.salesforcedx-vscode-ui-preview |
1.4.0 |
| salesforce.salesforcedx-vscode-visualforce |
67.7.1 |
| salesforce.sfdx-code-analyzer-vscode |
1.21.0 |
Most recent version of the extensions where this was working:
Not sure, had these issues for a while, but now the frustration accumulated so bad, I had to see what's under the hood. I think salesforcedx-lightning-lsp-common was shipped with this issue, as packages/salesforcedx-lightning-lsp-common/src/baseContext.ts always worked as described, from first commit: 3352a85, and packages/salesforcedx-vscode-lwc/src/languageClient/clientOptions.ts had the same issue from it's first commit too 54f6656
Summary
The LWC extension causes a continuous file-watcher load in large SFDX projects. Two defects combine.
writeSfdxJsconfig()writesjsconfig.jsonon every call. The function does not compare the new content with the old content.**/withignoreCreateEvents: false. The Apex and Lightning extensions useignoreCreateEvents: truefor the same purpose.The extension writes into the workspace. The file watcher sees the write. The watcher event starts a new index operation. The index operation regenerates the LWC typings. The regeneration creates more watcher events.
All CPU figures below use the percent-of-one-core convention. The
topcommand uses the same convention. My machine has 14 logical CPUs, thus the maximum is 1400%. The Windows Task Manager uses a different convention. It divides by the total capacity. Thus it shows a smaller number for the same load.Defect 1: unconditional write
packages/salesforcedx-lightning-lsp-common/src/baseContext.tsLine 383 reads the old content. Line 452 writes the new content. The code does not compare the two values.
updateForceIgnoreFile()in the same file uses the correct pattern.writeSfdxJsconfig()calls this function at line 466.Defect 2: watcher flag
Three packages create the same watcher. The LWC package is different from the other two.
The signature is
(pattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents). The comment says that the watcher observes deletions. The code also observes the creation of every directory in the workspace. https://code.visualstudio.com/api/references/vscode-api#workspaceSteps To Reproduce:
Open a large SFDX project in VS Code. My project has 191 LWC bundles and 496 Apex classes.
Make sure that
files.watcherExcludedoes not exclude.sfdx. This is the default condition.Do not use the editor for some minutes.
Run the PowerShell script below. The script samples the CPU time for 8 seconds. It reports both conventions.
Find the process with the highest
CPUsecvalue.Run
(Get-CimInstance Win32_Process -Filter "ProcessId=<PID>").CommandLinefor that process. The command line contains--utility-sub-type=node.mojom.NodeService. This process runs the file watcher.Look at the modification times of
force-app/main/default/lwc/jsconfig.json.Look at the modification times of the files in
.sfdx/typings/lwc/.Expected result
The file watcher uses almost no CPU while the editor is idle.
The extension writes
jsconfig.jsononly when the content changes.Actual result
The file-watcher process keeps one core to two cores busy while the editor is idle. The process accumulated 4.9 hours of CPU time in one session.
Two measurements from the same session follow. The editor was idle for both.
The machine has 14 logical CPUs. The process uses more than one thread, thus it uses more than one core.
The extension regenerates the 5,166
.d.tsfiles in.sfdx/typings/lwc/. These files andjsconfig.jsonget new modification times again and again. The content ofjsconfig.jsondoes not change.The extension host becomes unstable under this load. My
renderer.logfile has 915 entries that report an unresponsive extension host. The same log shows the exits of 14 different extension-host process IDs.The extension host starts
git.exechild processes. When the extension host exits, the child processes become orphans. I found 10 orphanedgit.exeprocesses. They stayed blocked for two days. Because of these processes, the built-in Git view and the diff view do not work correctly. Agit statuscommand in a terminal stays fast at 265 ms to 421 ms.Additional information
Suggested fix for defect 1
Compare the content before the write in
writeSfdxJsconfig():Suggested fix for defect 2
Set
ignoreCreateEventstotrueinclientOptions.tsat line 33. This value agrees with the comment. It also agrees with the Apex and Lightning packages.Workaround
Add
files.watcherExcludefor**/.sfdx/**to the workspace settings. This setting reduces the load, but it does not remove the cause.Note on
.forceignorewriteSfdxJsconfig()also fore appends two patterns to.forceignore. The patterns are**/jsconfig.jsonand**/.eslintrc.json. This write has the correct guard. I report it because the extension changes a file that is under source control. Maybe, the extension should be able to distinguish between developer tooling actual sources, so it does not tries to parse and deploy local configs, even if these are not even in a proper source folder or have associated*-meta.xmland related*.ts,*.js, or any other LWC specific source files with them. Check: jsconfig.jsonProject Info:
Project Info
Generated: 2026-08-07T05:55:07.771Z
Metadata
sourceApiVersion: 67.0
packageDirectories: 1
namespace:
Types
Org
Settings
Environment
Extensions (Salesforce)
Most recent version of the extensions where this was working:
Not sure, had these issues for a while, but now the frustration accumulated so bad, I had to see what's under the hood. I think salesforcedx-lightning-lsp-common was shipped with this issue, as
packages/salesforcedx-lightning-lsp-common/src/baseContext.tsalways worked as described, from first commit: 3352a85, andpackages/salesforcedx-vscode-lwc/src/languageClient/clientOptions.tshad the same issue from it's first commit too 54f6656