{"record":{"id":"f8d750fdcb310619","repo":"microsoft/aspire","slug":"dcp-fork-process-did-not-return-a-valid-child-process-id","errorCode":null,"errorMessage":"DCP fork-process did not return a valid child process ID. stdout: '{trimmedStdout}'","messagePattern":"DCP fork-process did not return a valid child process ID\\. stdout: '(.+?)'","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Cli/Processes/IsolatedProcess.Unix.cs","lineNumber":70,"sourceCode":"\n        try\n        {\n            // Once DCP has started, wait for it to report the detached child PID even if the caller\n            // cancels. Without the PID, callers cannot clean up a child that was already forked.\n            var stdoutLine = await stdoutLineTask.ConfigureAwait(false);\n            if (stdoutLine is null)\n            {\n                await dcpProcess.WaitForExitAsync(CancellationToken.None).ConfigureAwait(false);\n                var stderr = await stderrTask.ConfigureAwait(false);\n                throw new InvalidOperationException($\"DCP fork-process did not return a child process ID. DCP fork-process exited with code {dcpProcess.ExitCode}. stderr: '{stderr.Trim()}'\");\n            }\n\n            var trimmedStdout = stdoutLine.Trim();\n            // DCP fork-process writes only the detached child PID followed by a newline, for example:\n            //   12345\n            if (!int.TryParse(trimmedStdout, NumberStyles.None, CultureInfo.InvariantCulture, out var childPid))\n            {\n                throw new InvalidOperationException($\"DCP fork-process did not return a valid child process ID. stdout: '{trimmedStdout}'\");\n            }\n\n            ObserveDcpForkProcessStderr(stderrTask);\n\n            Process? childProcess;\n            try\n            {\n                childProcess = Process.GetProcessById(childPid);\n            }\n            catch (ArgumentException)\n            {\n                // A short-lived detached child can exit and be reaped by the DCP monitor between\n                // DCP printing its PID and this parent opening a Process handle. In that case the\n                // monitor process is the only remaining handle that can report the child's exit code.\n                await dcpProcess.WaitForExitAsync(CancellationToken.None).ConfigureAwait(false);\n                return new StartedProcess(\n                    dcpProcess,\n                    TextReader.Null,","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Cli/Processes/IsolatedProcess.Unix.cs#L52-L88","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the stdout text in the message to identify what DCP printed instead of the PID (banner, warning, error).","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.","Clear environment variables that could make dcp log to stdout (e.g. DOTNET_* logging verbosity overrides like COREHOST_TRACE or ASPNETCORE_logging).","Update Aspire CLI to the latest version so the fork-process output contract matches your DCP build.","If the issue persists, capture DCP's stderr (observed via ObserveDcpForkProcessStderr) and file an issue with both streams."],"exampleFix":"// before: DCP emits a banner line before the PID\n//   Aspire DCP v1.2.3\n//   12345\n// after: pin a matching DCP version / remove the banner-producing wrapper so the first stdout line is:\n//   12345","handlingStrategy":"try-catch","validationCode":"// Ensure the DCP launcher path exists and matches the CLI before starting\nif (!File.Exists(startInfo.DetachedUnixLauncherPath))\n{\n    throw new InvalidOperationException($\"DCP launcher not found at '{startInfo.DetachedUnixLauncherPath}'; update the Aspire CLI.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    var process = await isolatedProcess.StartAsync(...);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"did not return a valid child process ID\"))\n{\n    // log ex.Message (contains raw DCP stdout) and re-acquire/update DCP before retrying\n}","preventionTips":["Keep Aspire CLI and its DCP binary at matching versions; never hand-swap the dcp binary.","Avoid setting stdout-writing env overrides (COREHOST_TRACE, verbose logging) for CLI-spawned processes.","Don't wrap the dcp executable in scripts that print banners to stdout.","When upgrading Aspire, delete stale ~/.aspire/bin DCP caches so the CLI re-provisions."],"tags":["process","unix","dcp","pid-parsing"],"backgroundTag":"unexpected-response-shape","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}