Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 27, 2026

This change addresses item #4668

This changes visible behavior

The following changes are proposed:

  • Preset reloading no longer loops infinitely with symlinked files or include directives
  • Single debounced reapply (200ms) per file event burst instead of continuous reapply cycles
  • Watchers only recreated when the canonical file set changes, not on every reapply operation

The purpose of this change

Preset watchers caused infinite reload loops due to:

  1. Unconditional watcher disposal/recreation on every reapply
  2. Debouncing at FileWatcher layer created inter-event race conditions
  3. Missing path canonicalization caused duplicate watchers for symlinks
  4. Direct reapplyPresets() calls from event handlers bypassed debouncing

Solution: Three-layer fix

Controller-level debouncing:

// Single entry point with 200ms debounce
requestReapplyPresets(reason?: string) {
    if (this._isReapplyingPresets) {
        this._reapplyPending = true;  // Queue for after current reapply
        return;
    }
    this._reapplyDebounced();  // lodash.debounce(() => void this.reapplyPresets(), 200)
}

// All watcher events route here
["change", presetChangeHandler],   // → requestReapplyPresets('file change')
["unlink", presetChangeHandler],   // → requestReapplyPresets('file change')
["add", presetCreatedHandler]      // → requestReapplyPresets('file created')

Stable watcher identity:

// Canonicalize paths (resolve symlinks), deduplicate, sort for stable key
const canonical = await fs.realpath(filePath) || path.resolve(filePath);
const normalized = platform === 'win32' ? canonical.toLowerCase() : canonical;
// Deduplicate via Set, generate key
const key = canonicalPaths.sort().join('|');

// Only recreate if key changed
if (key !== this._watcherKey || !this._presetsWatchers) {
    this._presetsWatchers?.dispose();
    this._watcherKey = key;
    this._presetsWatchers = new FileWatcher(...);
}

Simplified FileWatcher:

  • Removed all debouncing logic (~40 lines)
  • Pure file watching: create watchers, attach handlers, dispose cleanly
  • Added awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 } for file write stability

Other Notes/Information

Pending request mechanism ensures no lost reapplies during concurrent operations:

finally {
    this._isReapplyingPresets = false;
    if (this._reapplyPending) {
        this._reapplyPending = false;
        this.requestReapplyPresets('pending');  // Trigger deferred request
    }
}

Why 200ms debounce: Collapses rapid events (editor saves, batch ops) while maintaining responsive UX.

Why original paths in FileWatcher: Chokidar handles symlinks internally via followSymlinks option. Canonical paths only used for stable key generation.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Infinity presets reloading</issue_title>
<issue_description>### Brief Issue Summary

I use only CMakeUserPresets.json without CMakePresets.json and got looped presets reloading with Cmake Tools 1.22.26

CMake Tools Diagnostics

{
  "os": "linux",
  "vscodeVersion": "1.109.0-insider",
  "cmtVersion": "1.22.26",
  "configurations": [
    {
      "folder": "/home/sr_team/Projects/Arizona/engine/dev",
      "cmakeVersion": "4.2.2",
      "configured": true,
      "generator": "Ninja",
      "usesPresets": true,
      "compilers": {}
    }
  ],
  "cpptoolsIntegration": {
    "isReady": false,
    "hasCodeModel": false,
    "activeBuildType": "",
    "buildTypesSeen": [],
    "requests": [],
    "responses": [],
    "partialMatches": [],
    "targetCount": 0,
    "executablesCount": 0,
    "librariesCount": 0,
    "targets": []
  },
  "settings": [
    {
      "communicationMode": "automatic",
      "useCMakePresets": "auto",
      "configureOnOpen": false
    }
  ]
}

Debug Log

Flooded with:


[presetController] Successfully expanded presets file /home/sr_team/Projects/Arizona/engine/dev/CMakeUserPresets.json
[main] Resolving the selected configure preset
[expand] expanded ${sourceDir}/build/${presetName}
[expand] expanded $env{ANDROID_NDK}/build/cmake/android.toolchain.cmake
[expand] expanded $env{ANDROID_NDK}
[extension] [4944] cmake.activeBuildPresetName started
[main] Loading new configure preset into CMake driver
[driver] Switching to configure preset: debug_android_x64
[driver] CMakeDriver configure preset set to debug_android_x64
[extension] [4944] cmake.activeBuildPresetName finished (returned "debug_android_x64")
[main] Resolving the selected build preset
[main] Using generator from user configuration: Ninja
[expand] expanded ${sourceDir}
[extension] [3135] cmake.activeBuildPresetName started
[main] Loading new build preset into CMake driver
[driver] Switching to build preset: debug_android_x64
[driver] CMakeDriver build preset set to debug_android_x64
[extension] [3135] cmake.activeBuildPresetName finished (returned "debug_android_x64")
[main] Resolving the selected test preset
[main] Resolving the selected test preset
[main] Using generator from user configuration: Ninja
[main] Loading new test preset into CMake driver
[driver] Switching to test preset: __defaultTestPreset__
[driver] CMakeDriver test preset set to __defaultTestPreset__
[main] Resolving the selected package preset
[main] Resolving the selected package preset
[main] Using generator from user configuration: Ninja
[main] Loading new package preset into CMake driver
[driver] Switching to package preset: __defaultPackagePreset__
[driver] CMakeDriver package preset set to __defaultPackagePreset__
[main] Resolving the selected workflow preset
[main] Resolving the selected workflow preset
[expand] expanded ${sourceDir}/build/${presetName}
[expand] expanded $env{ANDROID_NDK}/build/cmake/android.toolchain.cmake
[expand] expanded $env{ANDROID_NDK}
[main] Loading new workflow preset into CMake driver
[driver] Switching to workflow preset: __defaultWorkflowPreset__
[driver] CMakeDriver workflow preset set to __defaultWorkflowPreset__
[presetController] Reading presets file /home/sr_team/Projects/Arizona/engine/dev/CMakeUserPresets.json
[presetController] Reading and validating the presets file "/home/sr_team/Projects/Arizona/engine/dev/CMakeUserPresets.json"
[presetController] Successfully validated /home/sr_team/Projects/Arizona/engine/dev/CMakeUserPresets.json against presets schema
[presetController] Expanding presets file /home/sr_team/Projects/Arizona/engine/dev/CMakeUserPresets.json
[expand] expanded ${sourceDir}/build/${presetName}
[preset] Configure preset _ninja: No binaryDir specified, using default value "${sourceDir}/out/build/${presetName}"
[expand] expanded ${sourceDir}/out/build/${presetName}
[preset] Configure preset _debug: No binaryDir specified, using default value "${sourceDir}/out/build/${presetName}"
[expand] expanded ${sourceDir}/out/build/${presetName}
[preset] Configure preset _release: No binaryDir specified, using default value "${sourceDir}/out/build/${presetName}"
[expand] expanded ${sourceDir}/out/build/${presetName}
[preset] Configure preset _android: No binaryDir specified, using default value "${sourceDir}/out/build/${presetName}"
[expand] expanded ${sourceDir}/out/build/${presetName}
[expand] expanded $env{ANDROID_NDK}/build/cmake/android.toolchain.cmake
[expand] expanded $env{ANDROID_NDK}
[preset] Configure preset _clang: No binaryDir specified, using default value "${sourceDir}/out/build/${presetName}"
[expand] expanded ${sourceDir}/out/build/${presetName}
[expand] expanded ${sourceDir}/build/${presetName}
[expand] expanded ${sourceDir}/build/${presetName}
[expand] expanded ${sourceDir}/build/${presetName}
[expand] expanded ${sourceDir}/build/${presetName}
[expand] expanded $env{ANDROID...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes microsoft/vscode-cmake-tools#4668

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
Copilot AI changed the title [WIP] Fix infinity presets reloading issue in CMake Tools Jan 27, 2026
Copilot AI requested a review from hanniavalera January 27, 2026 15:18
@hanniavalera hanniavalera force-pushed the copilot/fix-infinity-presets-reloading branch from c92cc5f to 420f403 Compare January 28, 2026 14:47
@hanniavalera
Copy link
Contributor

@copilot update the changelog entry to be under 1.23 under Bug Fixes as opposed to under 1.22

Copy link
Contributor Author

Copilot AI commented Jan 28, 2026

@copilot update the changelog entry to be under 1.23 under Bug Fixes as opposed to under 1.22

Done. Moved the changelog entry to version 1.23 Bug Fixes section in commit 5feaa6d.

Copilot AI changed the title Fix infinite preset reloading when using only CMakeUserPresets.json Jan 28, 2026
Copilot AI changed the title Improve preset reloading: debounce all watcher events and reduce watcher churn Jan 28, 2026
@hanniavalera hanniavalera force-pushed the copilot/fix-infinity-presets-reloading branch 2 times, most recently from 356b123 to 5e34ced Compare January 29, 2026 16:35
Copilot AI and others added 4 commits January 30, 2026 08:23
Co-authored-by: hanniavalera <90047725+hanniavalera@users.noreply.github.com>
Co-authored-by: hanniavalera <90047725+hanniavalera@users.noreply.github.com>
…nd logging

Co-authored-by: hanniavalera <90047725+hanniavalera@users.noreply.github.com>
…her churn

Co-authored-by: hanniavalera <90047725+hanniavalera@users.noreply.github.com>
@hanniavalera hanniavalera force-pushed the copilot/fix-infinity-presets-reloading branch from 5e34ced to dfbc2fb Compare January 30, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants