Why Windows Search Freezes for 30 Seconds After Every Reboot

WINDOWS INTERNALS MINIFILTERS FRIDA DROPBOX
2026-04-15

Windows Search on four different Windows 11 machines was broken after every reboot — a 30-second blank window before results appeared. Different hardware, different user profiles, same symptom. Registry hacks, index rebuilds, driver updates, none of it helped. So I built custom Frida instrumentation, traced 46 SearchHost threads, and found a cascade of four interacting bottlenecks — ending with a kernel minifilter deadlock between the search indexer and Dropbox that nobody had documented before. The common denominator across all four machines was Dropbox with Smart Sync enabled.

Background

The primary test machine: Windows 11 Pro, build 26100, Ryzen 5 2400G, 14 GB RAM. But the same issue reproduced on three other Windows 11 PCs — a laptop, a workstation, and a family member's desktop. All different hardware, different Windows builds, different user profiles. The one thing they shared: Dropbox with Smart Sync.

The symptom was identical everywhere: hit Win+S, type something, stare at a blank pane for 20–30 seconds. Then suddenly results appear, and search works fine until the next reboot. Rebuilding the index, disabling Bing, re-registering AppX packages, resetting the WSearch service — the standard fixes from every forum thread did nothing. The problem always came back after reboot.

The breakthrough came from an observation on the primary machine: search started working exactly when the Dropbox tray icon finished loading. Every time. Once noticed, the same correlation was confirmed on the other three machines. That pointed to a boot-time dependency nobody was looking at.

Building the Instrumentation

Standard troubleshooting was useless because the symptom (blank search) has a hundred possible causes. I needed to see exactly what SearchHost.exe was doing during those 30 seconds. The approach:

  1. A Python tool using Windows APIs (NtQueryInformationThread, EnumProcessModulesEx, MiniDumpWriteDump) to enumerate all SearchHost threads, map their start addresses to loaded modules, and create a minidump for offline analysis
  2. Frida hooks (using Process.getModuleByName().getExportByName() — the static Module.getExportByName() is gone in Frida 17) on blocking calls: WaitForSingleObject, WaitForMultipleObjects, MsgWaitForMultipleObjectsEx, NtAlpcSendWaitReceivePort, CoCreateInstance, NdrClientCall3
  3. WPR (Windows Performance Recorder) with custom ETW profiles targeting Microsoft-Windows-Search-Core and related providers

The Python tracer kills SearchHost (simulating a cold boot), waits for it to respawn, attaches Frida, triggers Win+S via keybd_event, types a query via SendKeys, and records every blocking call per-thread for 20 seconds.

Thread Analysis Output (cold start)
Thread categories:
  ThreadPool     : 16 threads   // ntdll!TppWorkerThread
  unknown        : 7  threads   // shcore, tquery, CoreMessaging
  RPC/COM        : 2  threads   // combase.dll marshaling
  Graphics       : 2  threads   // atidxx64.dll + directmanipulation
  SearchUX       : 1  thread    // SearchHost.exe main
  XAML           : 1  thread    // Windows.UI.Xaml.dll
  WebView2       : 1  thread    // EmbeddedBrowserWebView.dll

Top blocking threads:
  TID 8380:  12,597ms  shcore.dll+0x47c50    // UI message pump
  TID 13628: 8,445ms   tquery.dll+0x153350   // search query engine

Two threads account for 21 of 22 seconds of total blocked time. The other 28 threads are either idle thread pool workers or waiting on these two. The 16 thread pool threads are a red herring — standard Windows TP workers, mostly asleep.

The Minifilter Stack

To understand the root cause, you need to understand how file I/O works on Windows. Every ReadFile call passes through a stack of minifilter drivers, each at a specific altitude (Microsoft's actual term, not mine). Higher altitude = intercepts first:

fltmc output — minifilter altitudes on the test machine
Filter Name          Altitude    Purpose
───────────────────  ──────────  ─────────────────────────────
bindflt              409800      Container/virtualization binding
UCPD                 385250      User-mode crash protection
WdFilter             328010      Windows Defender (antivirus band)
storqosflt           244000      Storage QoS
dbx                  186500      Secure Boot revocation
CldFlt               180451      Cloud Files (Dropbox, OneDrive)
bfs                  150000      Basic filesystem
Wof                   40700      Windows Overlay Filter (compression)
FileInfo              40500      File information/metadata

The metaphor: an I/O request is a ball falling from userspace to disk. Each filter is a net stretched across at a certain height. Higher altitude catches the ball first. Microsoft assigns altitude ranges by category — antivirus gets 320000–329999, cloud storage gets 180000–189999. You must register your altitude with Microsoft; no approval, no altitude.

The altitude is stored in the registry at HKLM\SYSTEM\CurrentControlSet\Services\{FilterName}\Instances\{Instance}\Altitude.

Bottleneck 1: Defender I/O Amplification

WdFilter.sys at altitude 328010 intercepts every file open/read/write from every process. When SearchIndexer reads a file for content indexing:

I/O amplification per indexed file
SearchIndexer ReadFile("main.rs")
   WdFilter intercepts, sends to MsMpEng for scanning    // I/O #2
   Defender approves, original read completes
   SearchFilterHost extracts text content
   Writes extracted text to Windows.db                    // I/O #3
   WdFilter intercepts the DB write too                   // I/O #4

Result: 4× I/O amplification per file indexed

With thousands of .rs, .js, .py files in dev repos, this creates sustained disk thrashing. Defender was accumulating 270 seconds of CPU while SearchIndexer was at 250 seconds — both hammering the same files simultaneously.

On this machine, Defender exclusions existed for C:\Users\Marty\ (old profile) but not C:\Users\marty.CHOPIN\ (current profile). Every dev file was being double-scanned.

Fix: Add-MpPreference -ExclusionProcess SearchIndexer.exe breaks the chain. Defender drops from hammering CPU to 0.02s over 3 seconds.

Bottleneck 2: The 1.6 GB Search Index

Windows 11 stores the search index in C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.db — a SQLite database. On this machine: 1,641 MB. Normal is 100–300 MB.

The bloat was caused by full-text content indexing of dev repos: every .rs, .js, .json, .py file's contents stored in the DB. The Frida trace showed tquery.dll (Microsoft Tripoli Query Engine) blocking for 8.4 seconds in a single WaitForMultipleObjects call while scanning this massive DB.

Fix: register the null persistent handler ({098f2470-bae0-11cd-b579-08002b30bfeb}) for code extensions. This tells SearchFilterHost to index filenames only, skip content extraction. Files are still findable by name; the DB drops from 1.6 GB to ~50 MB.

Bottleneck 3: WebView2 Cold Start

Windows 11's search UI is a web app. SearchHost.exe renders it inside an embedded Chromium browser via WebView2. Every boot, it spawns 6 msedgewebview2.exe processes consuming ~370 MB:

ProcessPurpose
BrowserMain coordinator, manages WebView2 lifecycle
GPUD3D/DirectX compositing for the search UI
RendererExecutes the JS search app from Cortana.UI/cache/WV2Local/
UtilityNetwork/services
CrashpadChromium crash handler
Spare rendererPre-spawned for next navigation

The Frida trace caught SearchHost polling registry keys in a loop during initialization:

Registry polling during WebView2 init (Frida RegQueryValueExW hook)
// Polled 21+ times each during the 5-second init window:
IsWebView2           OK    // "is WebView2 ready?"
SnrBundleVersion     OK    // "is the JS bundle loaded?"
WebView2Version      OK    // version check
MsbBundleVersion     MISS  // dead feature flag, fails every time
BINGIDENTITY_PROP_AUTHORITY OK  // Bing identity (even with Bing disabled!)

There is no registry key or ViVeTool feature ID that disables WebView2 in SearchHost on build 26100. IsWebView2=0 gets immediately overridden by the process. The XAML fallback was removed. This is a fixed ~5 second cost.

Bottleneck 4: The CldFlt Deadlock (Root Cause)

The previous three bottlenecks added up to maybe 15 seconds. The remaining time — and the reason it correlated with Dropbox — came from the cloud files minifilter.

Dropbox Smart Sync uses the Windows Cloud Files API (cfapi) to show placeholder files. These files appear in the filesystem with their original names and sizes, but have no local content. Every file has FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS (0x400000) set — reading the file content triggers a download through CldFlt.sys.

Cloud file state check (PowerShell)
# Check Dropbox placeholder state
$ Get-ChildItem "$env:USERPROFILE\Dropbox" -Recurse -File |
    Select -First 200 | ForEach {
      [uint32][IO.File]::GetAttributes($_.FullName) -band 0x400000
    } | Group | Select Count, Name

Count  Name
200    True     # ALL 200 sampled files: RECALL_ON_DATA_ACCESS

On this machine: 9,234 Dropbox files, every single one a placeholder.

The Deadlock Chain

At boot, WSearch starts before Dropbox finishes loading. SearchIndexer crawls the Dropbox folder and tries to extract content from each file for full-text indexing. Here's what happens in the minifilter stack:

I/O path through the minifilter stack
SearchIndexer ReadFile("proposal.docx")
   409800  bindflt        (pass-through)
   385250  UCPD           (pass-through)
   328010  WdFilter       Defender scans → MsMpEng re-reads
   244000  storqosflt     (pass-through)
   180451  CldFlt         "This is a placeholder!"
                              → issues CF_CALLBACK_TYPE_FETCH_DATA
                              → sends callback to Dropbox sync provider
                              → Dropbox is still starting
                              → no provider connected to CldFlt
                              → I/O BLOCKS (up to 60s timeout)
   150000  bfs            (never reached while blocked)
           NTFS / Disk    (never reached)

SearchIndexer.exe lives under %systemroot%, which means it bypasses the cloud file access denial (STATUS_CLOUD_FILE_ACCESS_DENIED, 0xC000CF18) that blocks most services from triggering hydration. It's "privileged" enough to trigger a download on every placeholder file.

The CldFlt FETCH_DATA callback has no receiver because Dropbox hasn't connected to the minifilter yet. Each blocked I/O can wait up to 60 seconds before timing out. With SearchFilterHost spawning threads for multiple files simultaneously, the thread pool saturates. This is why the Application Event Log showed 66 Event ID 10024 entries: "The filter host process did not respond and is being forcibly terminated."

The deadlock resolves itself when Dropbox finishes starting and connects its sync provider to CldFlt. Pending callbacks get serviced, blocked threads unblock, and search starts working. The user sees this as: search works as soon as the Dropbox tray icon appears.

The Fix

The fix targets all four bottlenecks:

BottleneckFixSurvives reboot?
CldFlt/Dropbox deadlock attrib +I on Dropbox folder (NTFS NOT_CONTENT_INDEXED) Yes (NTFS metadata)
Defender I/O amplification Add-MpPreference -ExclusionProcess SearchIndexer.exe Yes
1.6 GB index bloat Null persistent handler on 60 code extensions Until Windows Update
WebView2 cold start Scheduled task pre-warms search at login Yes

The critical fix is the first one. FILE_ATTRIBUTE_NOT_CONTENT_INDEXED (0x2000) tells SearchIndexer: index filenames and metadata (which doesn't trigger hydration), but skip content extraction (which does). Dropbox files are still findable by name. The attribute is NTFS metadata — it survives reboots, index rebuilds, and Windows Updates.

The one-line fix
attrib +I "C:\Users\%USERNAME%\Dropbox" /S /D

The Full Cascade

What makes this bug hard to diagnose is that it's not one problem — it's four independent issues that amplify each other:

Boot timeline
t=0s   Windows boots
t=2s   WSearch service starts (Automatic)
         SearchIndexer begins crawling user profile
         Hits Dropbox folder: 9,234 placeholder files
t=3s   SearchFilterHost tries content extraction
         CldFlt blocks → Dropbox not ready → threads hang
         WdFilter scans each file Defender tries to read → more blocking
t=5s   FilterHost threads pile up, start getting killed (Event 10024)
         Meanwhile: SearchHost spawning 6 WebView2 processes (370 MB)
         WebView2 polling IsWebView2 / SnrBundleVersion in a loop
t=8s   WebView2 initialized, but no search results available
         tquery.dll waiting on index that's being rebuilt
         Windows.db growing (content extraction still running on non-Dropbox files)
t=25s  Dropbox tray icon appears → provider connects to CldFlt
         Blocked I/O unblocks → pending FilterHost work completes
t=30s  Search starts working

Sidebar: The Altitude Arms Race

The minifilter altitude system is a "gentleman's agreement" enforced by Microsoft's altitude registration process. It works because everyone plays by the rules at the filter stack level. The real arms race — particularly in anti-cheat vs cheat — happens below:

The privilege ring escalation ladder
Ring  3    Userspace  apps, cheats (DLL inject, memory edit)
Ring  0    Kernel     drivers, anti-cheat, minifilters (WdFilter, CldFlt)
Ring -1    Hypervisor VT-x / AMD-V (Hyper-V, Riot Vanguard)
Ring -2    SMM        System Management Mode (Domas "Memory Sinkhole" 2015)
Ring -3    ME / PSP   separate CPU, own OS, DMA to everything

Ring -3 is Intel Management Engine (a full Minix 3 OS running on a separate x86 core inside every Intel CPU since 2008) or AMD Platform Security Processor (ARM Cortex-A5 with TrustZone). Both have DMA access to all system memory, run when the PC is "off," and execute firmware signed by the vendor's RSA key. The chipset encryption key was extracted in 2020 (CVE-2019-0090), so the firmware can be read, but the signing key has not been found — modified firmware won't execute.

Positive Technologies also discovered a hidden HAP (High Assurance Platform) bit that disables most ME functionality — traced to an NSA program. The NSA asked Intel for a kill switch for their own machines while everyone else runs ME unmodified.

How Everything Search Reads the MFT

The previous section showed that Everything bypasses the minifilter stack entirely. Here is the exact mechanism, because understanding it matters for building alternatives.

Everything opens the volume directly with CreateFile("\\\\.\\C:", ...), obtaining a volume handle — not a file handle. It then calls DeviceIoControl with FSCTL_ENUM_USN_DATA to walk the NTFS Master File Table record by record. Each record returns three fields: FileReferenceNumber, ParentFileReferenceNumber, and FileName. That is the complete dataset — metadata only, no file content is read at any point.

MFT enumeration via FSCTL_ENUM_USN_DATA
// Open volume handle (not a file handle)
HANDLE hVol = CreateFileW(
    L"\\\\.\\C:",
    GENERIC_READ,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL, OPEN_EXISTING, 0, NULL
);

// Walk the MFT record by record
MFT_ENUM_DATA_V0 med = { 0, 0, maxUsn };
while (DeviceIoControl(hVol, FSCTL_ENUM_USN_DATA,
        &med, sizeof(med), buf, bufSize, &bytesReturned, NULL)) {
    // Each USN_RECORD contains:
    //   FileReferenceNumber       — unique MFT index
    //   ParentFileReferenceNumber — parent directory MFT index
    //   FileName                  — file/directory name
    // NO content read. NO file handle opened. NO minifilter traversal.
}

The distinction is critical: minifilters like WdFilter and CldFlt register callbacks on IRP_MJ_CREATE (file open) and IRP_MJ_READ (file read) for individual file handles. A FSCTL_ENUM_USN_DATA call on a volume handle is a device I/O control request to the NTFS driver. It is not IRP_MJ_CREATE. The entire filter stack — every minifilter at every altitude — is bypassed. No Defender scan. No CldFlt hydration check. No content extraction. The NTFS driver reads its own internal data structures and returns the results directly.

For real-time updates after the initial scan, Everything calls DeviceIoControl(FSCTL_READ_USN_JOURNAL), which returns a stream of change records (file created, renamed, deleted) as they happen. This too operates on the volume handle, not on individual files.

As the Everything developer (voidtools handle "void") explained on the voidtools forum: FSCTL_ENUM_USN_DATA "does not walk through the change journal — this call walks through the MFT to identify files." The change journal is a separate structure that records modifications; the MFT is the filesystem's master allocation table. Everything reads the allocation table directly, then subscribes to the journal for incremental updates.

The performance difference is staggering. A fresh Windows 11 install has roughly 120,000 files. Everything indexes them all in approximately 1 second. Windows Search takes minutes to hours, because it opens every file individually, passes each through the minifilter stack, extracts text content via IFilter plugins, and writes the results to a SQLite database — which itself passes through the minifilter stack on every write.

Everything Windows Search
Initial scan ~1 second (120k files) Minutes to hours
Content indexing None (filenames only) Full text via IFilter plugins
Minifilter interaction None (volume-level FSCTL) Every file traverses full stack
Hydration trigger Never (no file open) Every cloud placeholder
Defender interaction None Scans every file opened + every DB write
Database format Custom binary (typically <50 MB) SQLite (Windows.db, often >1 GB)
Update mechanism USN journal monitoring (FSCTL) Filesystem change notifications + periodic recrawl

Methodology

Environment: Windows 11 Pro 10.0.26100, Ryzen 5 2400G (Vega 11), 14 GB RAM, Dropbox with Smart Sync (9,234 cloud-only files)

Tools used:

  • Frida 17.9.1 (Python bindings) — runtime instrumentation of SearchHost.exe. Hooks on WaitForSingleObject, WaitForMultipleObjects, MsgWaitForMultipleObjectsEx, NtAlpcSendWaitReceivePort, CoCreateInstance, CreateFileW, RegQueryValueExW, CreateProcessW, LoadLibraryExW
  • Custom Python tooling (ctypes) — NtQueryInformationThread for thread start addresses, EnumProcessModulesEx for module mapping, MiniDumpWriteDump for offline analysis
  • WPR (Windows Performance Recorder) — kernel ETW tracing with custom profile targeting Search-Core, Search-UI, and RPC providers
  • PowerShell — process monitoring, registry analysis, NTFS attribute manipulation, Defender configuration, scheduled task management
  • fltmc — minifilter enumeration and instance mapping

All tracing tools are available at github.com/martymonero/workbench/tree/main/win11_tuning.