Flow-Launcher/Flow.Launcher · error · SearchException

{e.Message}

Error message

{e.Message}

What it means

Thrown as SearchException('Windows Index', e.Message, e) from WindowsIndex.WindowsIndexSearchAsync when the (synchronous) setup of the async enumerable throws InvalidOperationException. Because the body is an iterator, only exceptions raised before the first `yield` are caught here - notably the regex `_reservedPatternMatcher.IsMatch(search)` call. OLE DB failures happen later (inside ExecuteWindowsIndexSearchAsync) and are logged/yield-broken there, not here.

Source

Thrown at Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndex.cs:87

            // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection.
        }

        internal static IAsyncEnumerable<SearchResult> WindowsIndexSearchAsync(
            string connectionString,
            string search,
            CancellationToken token)
        {
            try
            {

                return _reservedPatternMatcher.IsMatch(search)
                    ? AsyncEnumerable.Empty<SearchResult>()
                    : ExecuteWindowsIndexSearchAsync(search, connectionString, token);
            }
            catch (InvalidOperationException e)
            {
                throw new SearchException("Windows Index", e.Message, e);
            }
        }

        internal static bool PathIsIndexed(string path)
        {
            try
            {
                var csm = new CSearchManager();
                var indexManager = csm.GetCatalog("SystemIndex").GetCrawlScopeManager();
                return indexManager.IncludedInCrawlScope(path) > 0;
            }
            catch (COMException)
            {
                // Occurs because the Windows Indexing (WSearch) is turned off in services and unable to be used by Explorer plugin
                return false;
            }
        }
    }

View on GitHub (pinned to 7fc63b07bb)

Solutions

  1. Guard the search argument at the top: if (string.IsNullOrEmpty(search)) return AsyncEnumerable.Empty<SearchResult>();
  2. Move all OLE DB code below the first yield so failures stay inside ExecuteWindowsIndexSearchAsync and are logged, not rethrown as SearchException.
  3. If SearchException is thrown, inspect InnerException for the real InvalidOperationException stack.
  4. Add unit tests for the regex against adversarial inputs to make this branch unreachable.

Example fix

// before
internal static IAsyncEnumerable<SearchResult> WindowsIndexSearchAsync(string connectionString, string search, CancellationToken token)
{
    try
    {
        return _reservedPatternMatcher.IsMatch(search)
            ? AsyncEnumerable.Empty<SearchResult>()
            : ExecuteWindowsIndexSearchAsync(search, connectionString, token);
    }
    catch (InvalidOperationException e)
    {
        throw new SearchException("Windows Index", e.Message, e);
    }
}

// after - guard input and let OLE DB failures stay in the iterator
internal static IAsyncEnumerable<SearchResult> WindowsIndexSearchAsync(string connectionString, string search, CancellationToken token)
{
    if (string.IsNullOrEmpty(search) || _reservedPatternMatcher.IsMatch(search))
        return AsyncEnumerable.Empty<SearchResult>();
    return ExecuteWindowsIndexSearchAsync(search, connectionString, token);
}
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(search))
    return AsyncEnumerable.Empty<SearchResult>();

Type guard

static bool IsSearchable(string search) => !string.IsNullOrEmpty(search);

Try / catch

try { return WindowsIndex.WindowsIndexSearchAsync(connectionString, search, token); }
catch (SearchException ex) { App.API.LogException(nameof(WindowsIndex), $"{ex.EngineName} query failed", ex.InnerException); return AsyncEnumerable.Empty<SearchResult>(); }

Prevention

When it happens

Trigger: The reserved-pattern regex throws InvalidOperationException - in practice this is rare, but it can occur if the regex engine hits an internal state error or if the search string is null at a callsite that bypassed the empty check. More commonly this catch defends against future changes to the synchronous prologue of the iterator.

Common situations: A caller passes a null `search` (the IsMatch call then throws ArgumentNullException, not InvalidOperationException, so this is mostly defensive); a regex engine regression on a specific Unicode input; refactoring that moves OLE DB connection setup above the first yield would route OleDbException through this catch.

Related errors


AI-assisted analysis of Flow-Launcher/Flow.Launcher@7fc63b07bb (2026-08-13). Data as JSON: /api/errors/53b98372d8ac799e. Report an issue: GitHub.