microsoft/aspire · error · InvalidOperationException

Foundry CLI command ' ' could not be started.

Error message

Foundry CLI command '{command}' could not be started.

What it means

RunProcessAsync starts the `foundry` CLI as a child process and throws this InvalidOperationException when Process.Start() returns false, i.e. the OS refused to launch the executable. Since CreateFoundryStartInfo uses UseShellExecute=false with the literal executable name "foundry", this almost always means the foundry executable could not be resolved or executed on PATH.

Solutions

  1. Install the Foundry Local CLI and verify `foundry --version` works in the same shell/environment that launches the AppHost.
  2. If installed, add its install directory to the PATH visible to the app host process (check IDE/launchSettings environment, not just your terminal).
  3. On Linux/macOS, ensure the binary has the executable bit: chmod +x $(which foundry).
  4. Restart the IDE/terminal after installation so it picks up the updated PATH.

Example fix

// before: launch profile without foundry on PATH fails to start the CLI
// after: extend the app host's PATH so `foundry` resolves (launchSettings.json):
// "environmentVariables": { "PATH": "/usr/local/bin:%PATH%" }
// verify first:
// $ foundry --version
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight check the user can run before launching the AppHost:
public static bool IsFoundryCliAvailable()
{
    try
    {
        using var p = Process.Start(new ProcessStartInfo("foundry", "--version") { RedirectStandardOutput = true, UseShellExecute = false });
        return p is not null;
    }
    catch (System.ComponentModel.Win32Exception)
    {
        return false;
    }
}

Try / catch

try
{
    await foundry.StartAsync();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("could not be started"))
{
    throw new InvalidOperationException(
        "The 'foundry' CLI was not found on PATH. Install Foundry Local and restart your terminal/IDE.", ex);
}

Prevention

When it happens

Trigger: Any RunFoundryCommandCoreAsync call (model load, model info, service start/stop, daemon-verb probe) where Process.Start() returns false — typically because no `foundry` (or foundry.exe) binary is on the PATH of the app-host process, or the file exists but is not executable.

Common situations: Foundry Local CLI never installed on the dev machine; installed via a mechanism that doesn't add it to PATH; the app host runs in an environment (IDE launch profile, CI agent, container) with a different PATH than the user shell; on Linux/macOS the binary lacks the execute bit.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/7e0c3051d15e9fb7. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Foundry/FoundryLocalService.cs:401

            process,
            FormatCommand(arguments),
            onOutput,
            cancellationToken,
            stopReadingAfterProcessExit,
            outputCompletionPredicate).ConfigureAwait(false);
    }

    internal static async Task<FoundryCommandResult> RunProcessAsync(
        Process process,
        string command,
        Action<string>? onOutput,
        CancellationToken cancellationToken,
        bool stopReadingAfterProcessExit = false,
        Func<string, bool>? outputCompletionPredicate = null)
    {
        if (!process.Start())
        {
            throw new InvalidOperationException($"Foundry CLI command '{command}' could not be started.");
        }

        using var cancellationRegistration = cancellationToken.Register(static state => KillProcess((Process)state!), process);
        using var readCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        var outputCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

        void ProcessOutput(string line)
        {
            onOutput?.Invoke(line);
            if (outputCompletionPredicate?.Invoke(line) is true)
            {
                outputCompletionSource.TrySetResult();
            }
        }

        // Read both streams concurrently to avoid deadlock when a pipe buffer fills.
        var outputTask = ReadOutputAsync(process.StandardOutput, ProcessOutput, cancellationToken, readCancellation.Token);
        var errorTask = ReadOutputAsync(process.StandardError, ProcessOutput, cancellationToken, readCancellation.Token);

View on GitHub (pinned to 25830f84bd)