microsoft/aspire · error · InvalidOperationException
BrowserMessageStrings.BrowserLogsUnableToLocateBrowser
Error message
BrowserMessageStrings.BrowserLogsUnableToLocateBrowser
What it means
BrowserHostRegistry.AcquireAsync throws this before launching anything when ChromiumBrowserResolver.TryResolveExecutable cannot find a Chromium-family browser executable matching configuration.Browser. A tracked browser host needs a real browser binary; without one the lease cannot be created. It surfaces as an InvalidOperationException built from a localized resource string.
Solutions
- Install a supported Chromium-based browser (Chrome, Edge, or Chromium) on the machine.
- Verify the browser is discoverable: on PATH or in standard install locations for the OS.
- Request a Browser value that matches a browser actually installed (e.g. fall back from Chrome to Edge).
- Add browser installation steps to CI container images or setup scripts.
Example fix
// before
var config = new BrowserConfiguration { Browser = SupportedBrowser.Chrome }; // Chrome not installed
var lease = await registry.AcquireAsync(config, ct);
// after
var browser = ChromiumBrowserResolver.CanResolve(SupportedBrowser.Chrome)
? SupportedBrowser.Chrome
: SupportedBrowser.Edge; // install or select an available browser Defensive patterns
Strategy: validation
Validate before calling
// Verify a Chromium browser is available before acquiring
static bool HasChromiumBrowser() =>
OperatingSystem.IsWindows() && (File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Google/Chrome/Application/chrome.exe")) || File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft/Edge/Application/msedge.exe")))
|| OperatingSystem.IsLinux() && (File.Exists("/usr/bin/google-chrome") || File.Exists("/usr/bin/chromium") || File.Exists("/usr/bin/microsoft-edge"))
|| OperatingSystem.IsMacOS() && Directory.Exists("/Applications/Google Chrome.app"); Try / catch
try { lease = await registry.AcquireAsync(config, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("browser", StringComparison.OrdinalIgnoreCase)) { logger.LogWarning(ex, "No supported Chromium browser found; skipping browser logs."); } Prevention
- Install Chrome/Edge/Chromium in dev images and CI containers.
- Pin the browser type your app depends on in setup docs.
- Probe browser availability once at startup and log a clear warning instead of failing later.
When it happens
Trigger: Calling AcquireAsync (directly or via lease acquisition) with a BrowserConfiguration whose Browser value resolves to no installed executable: browser not installed, installed at a non-standard path, or running in a container/CI image without any Chromium browser.
Common situations: Fresh dev machine with no Chrome/Edge; slim Linux containers or GitHub Actions runners lacking browsers; user selects a specific browser type that is not installed; custom browser install location not on PATH or standard registry locations.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Browser profile ' ' matched multiple Chromium profiles…
- Browser profile ' ' was not found under ' '. Specify the…
- Browser user data directory
- Chromium profile metadata in
- Unable to read Chromium profile metadata from
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/1a996225fc9d02b9.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting.Browsers/BrowserHostRegistry.cs:57
TimeProvider timeProvider,
Func<BrowserConfiguration, string, BrowserLogsUserDataDirectory>? createUserDataDirectory,
Func<BrowserConfiguration, BrowserHostIdentity, BrowserLogsUserDataDirectory, CancellationToken, Task<IBrowserHost>>? createHostAsync,
bool enableEndpointMetadataAdoption = false)
{
_endpointDiscovery = new BrowserEndpointDiscovery(logger);
_createUserDataDirectory = createUserDataDirectory ?? CreateUserDataDirectory;
_createHostAsync = createHostAsync ?? CreateHostCoreAsync;
_enableEndpointMetadataAdoption = enableEndpointMetadataAdoption;
_logger = logger;
_timeProvider = timeProvider;
}
public async Task<BrowserHostLease> AcquireAsync(BrowserConfiguration configuration, CancellationToken cancellationToken)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
var browserExecutable = ChromiumBrowserResolver.TryResolveExecutable(configuration.Browser)
?? throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsUnableToLocateBrowser, configuration.Browser));
var userDataDirectory = _createUserDataDirectory(configuration, browserExecutable);
var identity = new BrowserHostIdentity(browserExecutable, userDataDirectory.Path);
// The core AcquireAsync flow has to make one atomic decision per browser identity:
//
// 1. If the registry already has a host for this executable + user data root, reuse it and increment the lease
// count.
// 2. Otherwise, create a host exactly once and publish it into the registry with the first lease.
//
// Keep the lock held across CreateHostCoreAsync. That method starts a new process by default, and can adopt a
// WebSocket endpoint when an explicit attach mode enables endpoint metadata. If two callers ran that decision
// concurrently they could both miss the dictionary entry and race to adopt/start a browser for the same profile.
var lockAcquired = false;
var hostPublished = false;
try
{
lockAcquired = await TryWaitForLockAsync(cancellationToken).ConfigureAwait(false);
ObjectDisposedException.ThrowIf(!lockAcquired, this);View on GitHub (pinned to 25830f84bd)