fix(build): disable Hybrid CRT by default #51432

Merged
LegendaryBlair opened 10:52am on October 20, 2026 wanted to merge 2 Δ into microsoft/powertoys main from
fix/disable-default-hybrid-crt-20261020

Pull Request Overview

  • Opened on October 20, 2026
  • Status Merged
  • Commit count 1 with first commit October 20, 2026

Total Delta

2 Total Diff Delta

Open Days

Open 16 weekdays

Test Delta

0 Diff Delta in Test Files
Breakdown by Phase

How long has this pull request spent in each phase of its lifecycle?

Fraction of total time Business days Phase
 
0.0 days Authoring 1 commit before pull request opened for review
 
0.0 days Awaiting first review
 
0.4 days Revising work with 0 commits in response to 2 reviews that left 1 comment
 
6.9 days Merge to deploy

Total time for pull request 7.3 business days from first commit authored to deploy

Author avatar

fix(build): disable Hybrid CRT by default

Summary of the Pull Request

Disable Hybrid CRT by default as a scoped rollback of the build-policy change in #50899, mitigating Runner exit hangs involving the shared UCRT exit-table lock.

Cpp.Build.props now defaults EnableHybridCRT to false when no value is supplied. Native projects return to private static UCRT linkage, while explicit opt-ins and independent project-level Hybrid CRT linker settings remain supported.

The New+ locale/environment-variable changes and the logger environment-variable fix from #50899 are intentionally retained. Only Cpp.Build.props and the directly related runtime guidance in doc/devdocs/development/guidelines.md change.

PR Checklist


  • [ ] Closes: N/A (partial rollback of #50899; no separate issue linked)
    <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) -->

  • [ ] Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected

  • [ ] Tests: Added/updated and all pass. No new application unit tests; native build and effective-linkage validation are listed below.

  • [x] Localization: All end-user-facing strings can be localized (N/A: no user-facing strings)

  • [x] Dev docs: Added/updated

  • [ ] New binaries: Added on the required places (N/A: no new binaries)



  • [ ] Documentation updated: If checked, please file a pull request on our docs repo and link it here: N/A (developer guidance is updated in this PR; no user documentation changes)

Detailed Description of the Pull Request / Additional comments

Why restore private UCRT linkage?

Matching-symbol inspection of installed 0.102.2883.0 found Runner performing static module destruction inside ucrtbase!execute_onexit_table. In one confirmed wait cycle:


  • The Runner main thread holds the CRT exit-table lock and joins the PowerDisplay executor while destroying the module.

  • The executor creates an ATL CString while sending the shutdown message. Lazy static initialization calls atexit/register_onexit_function, which needs the same shared CRT lock.

Other captured remnants show related exit-table/loader waits involving Light Switch listener cleanup and Mouse Highlighter DLL detach. Sharing the UCRT makes these previously separate runtime locks shared across the executable and module DLLs.

The reported working/failing boundary is 0.101.2362.0 to 0.102.2803.0. Git ancestry confirms that the latter includes #50899 but does not yet include #50897 or #51115. This is a build-policy mitigation, not a rewrite of Runner shutdown sequencing.

Scope and tradeoffs


  • Preserve /MT and /MTd; disable the default libucrt exclusion and shared-UCRT import-library selection by making their existing condition false.

  • Preserve CalculatorEngineCommon's explicit opt-in, the Command Palette Extensions SDK's independent Hybrid CRT linker configuration, and existing project opt-outs.

  • No CI/workflow/build-script override re-enables Hybrid CRT for Runner or its affected native modules.

  • Preserve the New+ and logger fixes, settings/IPC formats, and installed-user state.

  • Accept losing the default binary-size reduction until shutdown lifetimes are made safe for a shared CRT. No current installer-size measurement is claimed.

Validation Steps Performed

Native builds: passed

The following commands were run in the isolated worktree. All completed with exit code 0 after restoring the fresh worktree's missing native packages.

# PASS: native package restore.

& 'C:\Program Files\PowerShell\7\pwsh.exe' -NoProfile -File 'D:\PowerToys-hybrid-crt-rollback\tools\build\build.ps1' -Platform x64 -Configuration Release -Path 'D:\PowerToys-hybrid-crt-rollback\src\modules\powerdisplay\PowerDisplayModuleInterface' '/t:Restore' '/p:RestorePackagesConfig=true' '/p:SolutionDir=D:\PowerToys-hybrid-crt-rollback\' '/p:SkipLongPathsCheck=true'

# PASS: PowerDisplayModuleInterface and native dependencies; 0 errors, 0 warnings.
& 'C:\Program Files\PowerShell\7\pwsh.exe' -NoProfile -File 'D:\PowerToys-hybrid-crt-rollback\tools\build\build.ps1' -Platform x64 -Configuration Release -Path 'D:\PowerToys-hybrid-crt-rollback\src\modules\powerdisplay\PowerDisplayModuleInterface' '/p:SolutionDir=D:\PowerToys-hybrid-crt-rollback\' '/p:SkipLongPathsCheck=true'

# PASS: Runner and native dependencies; 0 errors, 1 host LIB-path warning.
& 'C:\Program Files\PowerShell\7\pwsh.exe' -NoProfile -File 'D:\PowerToys-hybrid-crt-rollback\tools\build\build.ps1' -Platform x64 -Configuration Release -Path 'D:\PowerToys-hybrid-crt-rollback\src\runner' '/restore' '/p:RestorePackagesConfig=true' '/p:SolutionDir=D:\PowerToys-hybrid-crt-rollback\' '/p:SkipLongPathsCheck=true'

The host has Windows long-path support disabled; the repository's documented SkipLongPathsCheck override was used for these scoped native builds without changing machine policy. Runner's warning was CS1668 for a nonexistent Visual Studio LIB search directory, not a source warning introduced by this change.

Effective compiler/linker configuration: 25 cases passed

A local-only MSBuild diagnostic target imported the original Cpp.Build.targets, added a non-built Link probe item to obtain evaluated item-definition metadata, and returned EnableHybridCRT, the actual ClCompile.RuntimeLibrary values, Link.IgnoreSpecificDefaultLibraries, and Link.AdditionalOptions.

For each project/configuration/platform row, the command was:

# PASS for all 24 matrix rows; $probe is the local ReportCrtLinkage.targets file.

& $msbuild $project /nologo /v:q /t:ReportCrtLinkage /getTargetResult:ReportCrtLinkage "/p:Platform=$platform" "/p:Configuration=$configuration" '/p:SolutionDir=D:\PowerToys-hybrid-crt-rollback\' "/p:ForceImportAfterCppTargets=$probe" /p:SkipLongPathsCheck=true

# PASS: additional Runner x64 Release positive control, using the same command with:
# /p:EnableHybridCRT=true

Projects
Configurations
Platforms
Result

Runner, PowerDisplayModuleInterface, LightSwitchModuleInterface, MouseHighlighter
Debug, Release
x64, ARM64
Private UCRT; /MTd or /MT; no shared-UCRT linker overrides

CalculatorEngineCommon
Debug, Release
x64, ARM64
Explicit Hybrid CRT opt-in retained

Command Palette Extensions SDK
Debug, Release
x64, ARM64
Independent Hybrid CRT linker configuration retained

Runner with explicit EnableHybridCRT=true
Release
x64
Opt-in still selects the shared UCRT

The four affected native projects and the positive control were evaluated with VS 2026 MSBuild. The two unchanged UWP opt-in projects were evaluated with installed VS 2022 MSBuild because the VS 2026 Build Tools installation lacks the UWP XAML targets. These are configuration evaluations, not ARM64 build/runtime passes.

Rebuilt binary imports: passed

# PASS: both dumpbin commands exited 0.

& $dumpbin /nologo /imports 'D:\PowerToys-hybrid-crt-rollback\x64\Release\PowerToys.exe'
& $dumpbin /nologo /imports 'D:\PowerToys-hybrid-crt-rollback\x64\Release\PowerToys.PowerDisplayModuleInterface.dll'

Both rebuilt binaries have no api-ms-win-crt-*, ucrtbase.dll, or ucrtbased.dll imports. This checks the resulting linkage, not only the property value.

git diff --check also passed.

Not claimed: application unit/UI test execution, a full installer or ARM64 build, or a post-change installed-product shutdown/restart A/B. The installed PowerToys binaries and existing hung-process evidence were not replaced or terminated.

Comments Threads Pending Resolution

ljharb reviewed on October 20, 2026
ljharb left a comment

### 🔵 Needs a closer look

The repository-wide linkage rollback needs human sign-off because rebuilt-product shutdown and restart behavior remains unvalidated.

**No open issues** 🎉

What changed in this PR

Partially rolls back #50899’s build policy to mitigate Runner exit hangs caused by shared UCRT locks.

**Changes:**
- Disables Hybrid CRT by default while preserving explicit opt-ins.
- Updates runtime guidance to explain private UCRT linkage and shutdown risks.
- Retains the earlier New+ and logger fixes.

| File | Description |
| ---- | ----------- |
| doc/​devdocs/​development/​guidelines.md | Documents the default linkage and Hybrid CRT precautions. |
| Cpp.Build.props | Defaults `EnableHybridCRT` to `false`. |

Review effort: Balanced

Resolved Comment Threads

No resolved comments have been left on this PR.