microsoft/aspire · error · InvalidOperationException

DCP fork-process did not return a valid child process ID…

Error message

DCP fork-process did not return a valid child process ID. stdout: '{trimmedStdout}'

What it means

Aspire CLI launches detached child processes on Unix by invoking `dcp fork-process --monitor <pid> ...`, which must print exactly one line containing the detached child's PID. When that line cannot be parsed as a plain non-negative integer, the CLI throws this InvalidOperationException because it cannot obtain a process handle or perform any cleanup without a PID. The raw stdout is included in the message so you can see what DCP actually printed instead.

Solutions

  1. Inspect the stdout text in the message to identify what DCP printed instead of the PID (banner, warning, error).
  2. Ensure the DCP binary used (DetachedUnixLauncherPath / ASPIRE_DCP path config) matches the CLI version; delete stale ~/.aspire/bin/dcp copies and let the CLI re-acquire DCP.
  3. Clear environment variables that could make dcp log to stdout (e.g. DOTNET_* logging verbosity overrides like COREHOST_TRACE or ASPNETCORE_logging).
  4. Update Aspire CLI to the latest version so the fork-process output contract matches your DCP build.
  5. If the issue persists, capture DCP's stderr (observed via ObserveDcpForkProcessStderr) and file an issue with both streams.

Example fix

// before: DCP emits a banner line before the PID
//   Aspire DCP v1.2.3
//   12345
// after: pin a matching DCP version / remove the banner-producing wrapper so the first stdout line is:
//   12345
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the DCP launcher path exists and matches the CLI before starting
if (!File.Exists(startInfo.DetachedUnixLauncherPath))
{
    throw new InvalidOperationException($"DCP launcher not found at '{startInfo.DetachedUnixLauncherPath}'; update the Aspire CLI.");
}

Try / catch

try
{
    var process = await isolatedProcess.StartAsync(...);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("did not return a valid child process ID"))
{
    // log ex.Message (contains raw DCP stdout) and re-acquire/update DCP before retrying
}

Prevention

When it happens

Trigger: StartDetachedUnixAsync (called via StartAsync) reads one line from the DCP fork-process stdout and gets a line that fails int.TryParse under NumberStyles.None — e.g. an empty/whitespace line, a version banner, a warning or log message emitted before/instead of the PID, a localized message, or a negative number.

Common situations: A mismatched or corrupted DCP binary on PATH (different Aspire CLI/DCP version that prints extra output), an outdated Aspire CLI after a DCP protocol change, DCP emitting deprecation or environment warnings to stdout, or a wrapper script around dcp that adds output.

Related errors


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

Appendix: source

Thrown at src/Aspire.Cli/Processes/IsolatedProcess.Unix.cs:70

        try
        {
            // Once DCP has started, wait for it to report the detached child PID even if the caller
            // cancels. Without the PID, callers cannot clean up a child that was already forked.
            var stdoutLine = await stdoutLineTask.ConfigureAwait(false);
            if (stdoutLine is null)
            {
                await dcpProcess.WaitForExitAsync(CancellationToken.None).ConfigureAwait(false);
                var stderr = await stderrTask.ConfigureAwait(false);
                throw new InvalidOperationException($"DCP fork-process did not return a child process ID. DCP fork-process exited with code {dcpProcess.ExitCode}. stderr: '{stderr.Trim()}'");
            }

            var trimmedStdout = stdoutLine.Trim();
            // DCP fork-process writes only the detached child PID followed by a newline, for example:
            //   12345
            if (!int.TryParse(trimmedStdout, NumberStyles.None, CultureInfo.InvariantCulture, out var childPid))
            {
                throw new InvalidOperationException($"DCP fork-process did not return a valid child process ID. stdout: '{trimmedStdout}'");
            }

            ObserveDcpForkProcessStderr(stderrTask);

            Process? childProcess;
            try
            {
                childProcess = Process.GetProcessById(childPid);
            }
            catch (ArgumentException)
            {
                // A short-lived detached child can exit and be reaped by the DCP monitor between
                // DCP printing its PID and this parent opening a Process handle. In that case the
                // monitor process is the only remaining handle that can report the child's exit code.
                await dcpProcess.WaitForExitAsync(CancellationToken.None).ConfigureAwait(false);
                return new StartedProcess(
                    dcpProcess,
                    TextReader.Null,

View on GitHub (pinned to 25830f84bd)