Skip to content

feat(dotnet11): add process-api-net11 skill for new process APIs - #866

Open
shaikhsharukh wants to merge 17 commits into
dotnet:mainfrom
shaikhsharukh:contrib/process-api-net11
Open

shaikhsharukh wants to merge 17 commits into
dotnet:mainfrom
shaikhsharukh:contrib/process-api-net11

Conversation

@shaikhsharukh

Copy link
Copy Markdown
Contributor

Resolves issue #649 by adding a new skill process-api-net11 for the new System.Diagnostics.Process APIs introduced in .NET 11.

This skill guides coding agents on using the new convenience one-liners, reliable deadlock-free output reading, and parent-child process lifetime properties. Evals have been included and verified using the skill-validator.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Note

This PR is from a fork and modifies infrastructure files (eng/ or .github/).

Changes to infrastructure typically need to be submitted from a branch in dotnet/skills (not a fork) so that CI workflows run with the correct permissions and secrets.

Please consider recreating this PR from an upstream branch. If you don't have push access to dotnet/skills, ask a maintainer to push your branch for you.

@shaikhsharukh

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

@shaikhsharukh
shaikhsharukh force-pushed the contrib/process-api-net11 branch from bb05fea to 29286e7 Compare July 7, 2026 12:14
@rickgonzalez

Copy link
Copy Markdown

A short narrated explainer of this PR (dev testing my tool of public repos) - I hope you find it helpful. https://www.lenzon.ai/viewer/cmrb1zapc000o14os7auramuh?voice=google-chirp3

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

🤖 Posted by GitHub Copilot on behalf of the repo maintainers.

Thanks for this contribution, @shaikhsharukh! 🎉 I verified every API in this skill against dotnet/runtime main (which is the .NET 11 branch — eng/Versions.props shows ProductVersion 11.0.0; no release/11.0 branch exists yet), and the good news is these are all genuinely shipping .NET 11 APIs — Process.Run/RunAsync, RunAndCaptureText/Async, StartAndForget, the ReadAll* family, and the new ProcessStartInfo properties. Nice job picking a real, useful new surface. 👍

Before we run evals and move toward merge, though, the SKILL.md documents several of these APIs inaccurately versus the public ref assembly. Since a skill trains agents to emit code, these would lead to non-compiling output. Could you correct the following?

1. ReadAll* return types are wrong. They return tuples / streaming types, not plain strings/arrays:

Documented Actual
string ReadAllText() (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = default)
byte[] ReadAllBytes() (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(...)
string[] ReadAllLines() IEnumerable<ProcessOutputLine> ReadAllLines(...)
Task<string[]> ReadAllLinesAsync() IAsyncEnumerable<ProcessOutputLine> ReadAllLinesAsync(...)
Task<string> ReadAllTextAsync() Task<(string, string)> ReadAllTextAsync(...)

2. InheritedHandles — invented type. The doc uses ProcessHandleInheritance InheritedHandles, but that type doesn't exist. The real property is IList<SafeHandle>? InheritedHandles.

3. Static method signatures. Run, RunAndCaptureText, and StartAndForget don't take (string arguments = "", ProcessStartInfo? startInfo = null). Actual: arguments is IList<string>? with an optional TimeSpan? timeout (Run also has a bool silent parameter); StartAndForget has no startInfo trailing parameter.

4. Example 1 won't compile. Process.RunAndCaptureTextAsync("git", "--version") — a bare string can't bind to IList<string>?. Use ["--version"] (or pass a ProcessStartInfo).

5. Example 3 won't compile. string[] lines = await process.ReadAllLinesAsync(); foreach (var line in lines) — ReadAllLinesAsync() returns IAsyncEnumerable<ProcessOutputLine>, so it needs await foreach, and each item is a ProcessOutputLine, not a string.

For reference, Example 2 (KillOnParentExit = true) and the ProcessTextOutput/ProcessExitStatus usage in Example 1 are correct as written. ✅

Once the signatures and examples are aligned with the ref assembly, we'll kick off evals and continue the review. Thanks again for contributing! 🙏

@shaikhsharukh
shaikhsharukh force-pushed the contrib/process-api-net11 branch from 8c1066b to 2ae8051 Compare July 11, 2026 00:26
@shaikhsharukh

Copy link
Copy Markdown
Contributor Author

I have aligned the skill definitions and examples with the dotnet/runtime ref assembly:

  1. ReadAll* Return Types: Corrected the signatures to match the exact tuple return types ((string StandardOutput, string StandardError) for ReadAllText/Async, (byte[] StandardOutput, byte[] StandardError) for ReadAllBytes/Async) and streaming types (IEnumerable<ProcessOutputLine> / IAsyncEnumerable<ProcessOutputLine> for ReadAllLines/Async).
  2. InheritedHandles Property: Changed the property type from the hypothetical ProcessHandleInheritance to IList<SafeHandle>?.
  3. Static Method Signatures: Aligned the static Run, RunAndCaptureText, and StartAndForget methods to accept IList<string>? arguments and appropriate timeout/silent parameters.
  4. Example 1 Compilation: Modified the call to pass ["status"] as a collection expression instead of a bare string parameter.
  5. Example 3 Compilation: Updated the ping example to use await foreach over IAsyncEnumerable<ProcessOutputLine> and access line.Content / line.StandardError.

@AbhitejJohn AbhitejJohn left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review — API accuracy re-verified + eval-hardening suggestions

Thanks for the fixes, @shaikhsharukh. I re-checked everything and ran a deeper evaluation pass. Summary below; concrete suggestions are inline on eval.yaml.

1. API accuracy — all confirmed ✅ (proven)
I re-verified every signature in SKILL.md against the current dotnet/runtime main reference assemblies. All five items from the earlier review are now correct: the tuple-returning ReadAllText/ReadAllTextAsync → (string StandardOutput, string StandardError), streaming ReadAllLinesAsync → IAsyncEnumerable<ProcessOutputLine>, RunAndCaptureText(Async) → ProcessTextOutput, and KillOnParentExit. Content looks accurate.

2. Does the skill actually help? — yes, in plugin/deployment mode (proven, scoped)
I hardened the eval to use intent-only prompts (never naming the API) and ran it with skill-validator (--runs 5, judge = claude-opus-4.6). In the whole-plugin configuration (how the skill actually ships), it produced large, low run-to-run variance (CV 1–6%) gains on its target APIs vs a no-skill baseline:

Scenario Baseline With skill
Run + capture (RunAndCaptureText) 1.8 / 5 4.2 / 5
Full read (ReadAllText tuple) 1.0 / 5 4.2 / 5
Stream lines (ReadAllLinesAsync) 1.4 / 5 4.2 / 5

The no-skill baseline reached for the pre-.NET-11 pattern (StandardOutput.ReadToEndAsync() + Task.WhenAll); the judge noted it "didn't even attempt to research what new APIs .NET 11 offers." So the skill is teaching genuinely non-obvious APIs — that's its value.

3. Honest caveat — the blended eval verdict is not green
Being transparent: the validator's official pass metric doesn't clear the 0.10 bar (this skill scored 0.065). The plugin-mode wins above are real, but the isolated single-skill arm shows only small lift and the skill roughly doubles output tokens — both of which the weighted score penalizes. So it's a strong deployment-mode signal with a failing blended score; flagging for maintainers to weigh rather than claiming a clean pass.

4. Suggested eval improvements (inline suggestions)

  • Strengthen the run+capture assertions to require both .StandardOutput and .StandardError and reject the old event model.
  • Add two scenarios that hit the harder, wrong-signature-prone surface — the tuple-returning ReadAllText and streaming ReadAllLinesAsync / await foreach. These are where the skill adds the most measurable value.
  • Add a subtle net10.0 non-activation negative (the new APIs are net11-only).
  • The existing KillOnParentExit scenario is fine as coverage, but note it shows ~0 marginal lift — the base model already emits it correctly — so it isn't what demonstrates the skill's value.

cc @lewing @SamMonoRT — would appreciate your read on point 3: the deployment-mode gains are real and stable, but the blended eval score doesn't clear the gate. Is the plugin-mode evidence enough to accept, or do you want the eval reworked to a clean green first?

Comment thread tests/dotnet11/process-api-net11/eval.yaml Outdated
Comment thread tests/dotnet11/process-api-net11/eval.yaml
Comment thread tests/dotnet11/process-api-net11/eval.yaml Outdated
Comment thread tests/dotnet11/process-api-net11/eval.yaml Outdated
@shaikhsharukh

Copy link
Copy Markdown
Contributor Author

I have applied all of the suggested evaluation improvements:

  1. Hardened run+capture assertions: Updated to verify that both .StandardOutput and .StandardError are accessed, and blocked usage of the legacy event-based BeginOutputReadLine / OutputDataReceived patterns.
  2. Added ReadAllText (tuple return) scenario: Created a new scenario verifying that agents properly destructure the (StandardOutput, StandardError) tuple when reading process output via instance methods.
  3. Added ReadAllLinesAsync scenario: Added a streaming scenario verifying that agents consume IAsyncEnumerable<ProcessOutputLine> using await foreach and read .Content / .StandardError properly.
  4. Broadened non-activation guards: Updated the .NET 8 negative scenario to block leakages of any of the new .NET 11 APIs (RunAndCaptureText, ReadAllTextAsync, ReadAllLinesAsync, KillOnParentExit).
  5. Added .NET 10 negative scenario: Added a subtle negative scenario verifying that agents correctly fallback to pre-.NET 11 redirect and stream-reader APIs when targeting net10.0.

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

/evaluate

@github-actions

Copy link
Copy Markdown
Contributor

❌ Evaluation did not complete successfully (the evaluate job reported failure). Check the workflow run logs, then re-post /evaluate to try again.

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

@eiriktsarpalis could you please review this PR? You did the deepest review on the closely analogous #535 (new dotnet11 plugin skill + evals), so your feedback here would be much appreciated.

@eiriktsarpalis

Copy link
Copy Markdown
Member

It might be best if @adamsitnik took a look.

@SamMonoRT

Copy link
Copy Markdown
Member

@adamsitnik will review once back from vacation. Please don't merge without his approval.

@AbhitejJohn
AbhitejJohn requested a review from adamsitnik August 7, 2026 18:27
@AbhitejJohn

Copy link
Copy Markdown
Collaborator

@adamsitnik , @SamMonoRT : Gentle ping on reviewing the changes here.

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

/evaluate

@github-actions

Copy link
Copy Markdown
Contributor

👋 /evaluate needs the exact commit to evaluate.

Two ways to run it:

  1. Review flow (recommended — no SHA to copy): open Files changed → Review changes, type /evaluate in the review box, and Submit review. GitHub binds the run to the exact commit you reviewed.
  2. Comment flow: copy-paste this command for the current head:
/evaluate 630086d2db22c86f5ff7b467fc808d44d131dc67

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

/evaluate 630086d

@github-actions

Copy link
Copy Markdown
Contributor

🔒 Secret-backed evaluation is disabled for fork PRs. A maintainer must review and promote the change to a trusted repository branch before running /evaluate. View workflow run

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shaikhsharukh big thanks for your contribution! PTAL at my comments.

## When to Use

- Running or orchestrating external processes in a .NET 11 (or later) project.
- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureTextAsync`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we provide sync and async overloads

Suggested change
- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureTextAsync`).
- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureText[Async]`).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated bullet to mention Process.RunAndCaptureText[Async].

- Running or orchestrating external processes in a .NET 11 (or later) project.
- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureTextAsync`).
- Wanting to ensure child processes are automatically terminated when the parent process exits (`KillOnParentExit`).
- Requiring trimmer-friendly and NativeAOT-compatible process creation via `SafeProcessHandle`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording needs a bit more specific. Process itself is NativeAOT-compatible, it's just that with SafeProcessHandle the user gets the smallest possible size on disk (so it's more of an optimization)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified that SafeProcessHandle is an optimization for minimal disk footprint with NativeAOT.

## When Not to Use

- The project targets .NET 10 or earlier — these APIs are not available before .NET 11.
- Running simple shells where custom execution code is unnecessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a native speaker, so please take it with a grain of salt. But overall I do believe that the new APIs simplify the code a lot, so I would recommend them even for simple shells.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the bullet against simple shells.

<TargetFramework>net11.0</TargetFramework>
```

## New APIs & Convenience Methods

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these methods is a new API, some are also convenience methods. I would just call it New APIs

Suggested change
## New APIs & Convenience Methods
## New APIs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed section heading to ## New APIs.

### High-Level Convenience APIs (Static Methods)

#### `Process.Run` / `Process.RunAsync`
Starts a process and waits for it to exit, returning the exit status. Does not capture standard output or error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to mention that it also allows the users to discard the output/error by providing silent: true and internally redirecting std handles to NUL device.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned that silent: true discards output/error by internally redirecting std handles to NUL.

```

#### `StartDetached`
Starts the process detached from the parent's terminal or job session, ensuring it survives the parent's exit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When set to true, the std handles are by default redirected to NUL device. So the child process does not keep the parent process console/terminal resources alive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added note explaining standard handles redirection to the NUL device when Silent is true.


if (result.ExitStatus.ExitCode == 0)
{
Console.WriteLine($"Git Output: {result.StandardOutput.Trim()}");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use Trim here.

Suggested change
Console.WriteLine($"Git Output: {result.StandardOutput.Trim()}");
Console.WriteLine($"Git Output: {result.StandardOutput}");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Removed .Trim().

using System.Threading.Tasks;

// Run 'git status' and capture output (arguments passed as list)
ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", ["status"]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

People tend to use async for apps that absolutely don't benefit from it (scalability, cancellation support etc). I think it's better to use non-async overload for such simple examples. So AI-written code is simple and does not pay for the price of using async

Suggested change
ProcessTextOutput result = await Process.RunAndCaptureTextAsync("git", ["status"]);
ProcessTextOutput result = Process.RunAndCaptureText("git", ["status"]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated the example to use the synchronous Process.RunAndCaptureText overload.


var startInfo = new ProcessStartInfo("dotnet", ["run", "--project", "BackgroundWorker.csproj"])
{
KillOnParentExit = true // Auto-teardown when this parent process exits

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
KillOnParentExit = true // Auto-teardown when this parent process exits
KillOnParentExit = OperatingSystem.IsWindows() || OperatingSystem.IsLinux() // Auto-teardown when this parent process exits

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added OS platform checks: KillOnParentExit = OperatingSystem.IsWindows() || OperatingSystem.IsLinux().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be addressed.

```csharp
using System.Diagnostics;

var startInfo = new ProcessStartInfo("dotnet", ["run", "--project", "BackgroundWorker.csproj"])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: subjective: no need to use var

Suggested change
var startInfo = new ProcessStartInfo("dotnet", ["run", "--project", "BackgroundWorker.csproj"])
ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated the snippet to explicitly declare ProcessStartInfo.

@shaikhsharukh

Copy link
Copy Markdown
Contributor Author

Thank you @adamsitnik for the thorough review! All feedback has been addressed and pushed in the latest commit: updated argument types to IEnumerable<string>?, documented return types, clarified NativeAOT/lifecycle/handle rules, and refined the sample snippets.

- Running or orchestrating external processes in a .NET 11 (or later) project.
- Needing to start a process, wait for it to exit, and capture its output/error streams without risking deadlocks (`Process.RunAndCaptureText[Async]`).
- Wanting to ensure child processes are automatically terminated when the parent process exits (`KillOnParentExit`).
- Requiring trimmer-friendly and NativeAOT-optimized process creation via `SafeProcessHandle` for the smallest possible disk footprint.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SafeProcessHandle is more lightweight API than full Process. It has better performance characteristics on all form-factors. It is not a NativeAOT specific optimization.

@shaikhsharukh

Copy link
Copy Markdown
Contributor Author

Thank you @jkotas for the clarification! Updated the documentation in the latest commit to accurately reflect that SafeProcessHandle is a lightweight, low-overhead API providing better performance characteristics across all form-factors and runtimes, rather than framing it as a NativeAOT-specific optimization.

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

@jkotas , @adamsitnik : Could you please take another pass at this PR? Looks like the author has addressed all the feedback and this PR has been sitting for a bit.

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's almost ready, but needs some edits first. Thank you for your contribution @shaikhsharukh !

Comment on lines +43 to +46
public readonly record struct ProcessExitStatus(int ExitCode)
{
public bool Success => ExitCode == 0;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong:

  • ProcessExitStatus consists of 3 properties
  • We don't provide Success on purpose, as we can't assume what exit codes are used to represent success/failure for every command line app. For example, somebody can return -1 on success.
Suggested change
public readonly record struct ProcessExitStatus(int ExitCode)
{
public bool Success => ExitCode == 0;
}
public readonly record struct ProcessExitStatus(int ExitCode, bool Canceled, PosixSignal? Signal = null);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated struct definition to public readonly record struct ProcessExitStatus(int ExitCode, bool Canceled, PosixSignal? Signal = null); and removed the Success property.

```
- **`ProcessTextOutput`**: Contains the exit status along with all captured standard output and standard error text.
```csharp
public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError);
public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError, int ProcessId);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated struct definition to public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError, int ProcessId);.


### Types

Before using the new convenience methods, note the following return and record structures:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of them is a record.

Suggested change
Before using the new convenience methods, note the following return and record structures:
Before using the new convenience methods, note the following return and structures:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated section header to refer to return structures instead of records.

public static ProcessExitStatus Run(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add a note saying that on timeout/cancellation the process is killed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added note specifying that on timeout or cancellation, the process is killed.

public static ProcessTextOutput RunAndCaptureText(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add a note saying that when using the ProcessStartInfo overloads, the user is responsible for setting output and error redirected via the boolean flags (because BCL APIs can't modify the arguments they were given).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added note clarifying that callers using ProcessStartInfo overloads must set RedirectStandardOutput = true and RedirectStandardError = true on ProcessStartInfo.

public static int StartAndForget(string fileName, IEnumerable<string>? arguments = null)
public static int StartAndForget(ProcessStartInfo startInfo)
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another note needed: by default, when output/error redirection was not specified, the StartAndForget method will redirect all standard handles to NUL device.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added note explaining that by default, when output/error redirection is not specified, Process.StartAndForget redirects all standard handles to the NUL device.

### ProcessStartInfo Properties

#### `KillOnParentExit`
Ensures that the spawned child process is terminated when the current (parent) process exits. Works across Windows, Linux, and Android.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Ensures that the spawned child process is terminated when the current (parent) process exits. Works across Windows, Linux, and Android.
Ensures that the spawned child process is terminated when the current (parent) process exits (including fatal crash and being force killed). Works across Windows, Linux, and Android.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated description to: 'Ensures that the spawned child process is terminated when the current (parent) process exits (including fatal crash and being force killed). Works across Windows, Linux, and Android.'

```

#### `InheritedHandles`
Provides precise control over which file/kernel handles are inherited by the child process, preventing accidental resource leaks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Provides precise control over which file/kernel handles are inherited by the child process, preventing accidental resource leaks.
Provides precise control over which handles (file descriptors) are inherited by the child process, preventing accidental resource leaks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated description to: 'Provides precise control over which handles (file descriptors) are inherited by the child process, preventing accidental resource leaks.'

Comment on lines +122 to +127
#### `Silent`
When set to `true`, the standard handles are by default redirected to the `NUL` device, ensuring the child process does not keep parent console or terminal resources alive.
```csharp
public bool Silent { get; set; }
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessStartInfo does not offer such property, it's just an argument for the Run[Async] methods

Suggested change
#### `Silent`
When set to `true`, the standard handles are by default redirected to the `NUL` device, ensuring the child process does not keep parent console or terminal resources alive.
```csharp
public bool Silent { get; set; }
```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed Silent from ProcessStartInfo Properties (it is retained only as a parameter on Process.Run[Async]).

Comment on lines +192 to +201
using Process? process = Process.Start(startInfo);
if (process != null)
{
// Read all output lines safely and asynchronously
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
string prefix = line.StandardError ? "[Err]" : "[Out]";
Console.WriteLine($"{prefix} > {line.Content}");
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a PITA, but Process.Start can return null only when we set UseShellExecute = true.

Suggested change
using Process? process = Process.Start(startInfo);
if (process != null)
{
// Read all output lines safely and asynchronously
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
string prefix = line.StandardError ? "[Err]" : "[Out]";
Console.WriteLine($"{prefix} > {line.Content}");
}
}
using Process process = Process.Start(startInfo)!;
// Read all output lines safely and asynchronously
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
string prefix = line.StandardError ? "[Err]" : "[Out]";
Console.WriteLine($"{prefix} > {line.Content}");
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated Example 3 to use using Process process = Process.Start(startInfo)!;.

@pamod-madubashana pamod-madubashana left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a process-api-net11 skill covering the new .NET 11 Process APIs with examples and evals. Not ready to merge as-is: the eval uses the retired schema so it loads zero stimuli, and the KillOnParentExit example contradicts its own doc and fails its own grader.

@@ -0,0 +1,140 @@
scenarios:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran this eval through the repo quality gate and it counts zero stimuli because the file uses the retired scenarios/assertions shape instead of the current stimuli/graders/defaults shape, so it can never produce a verdict and the gate reports 0 preference stimuli needing 5 more. The sibling system-text-json-net11 eval shows the expected layout with exit-success and output-matches graders carrying a config pattern. Please rewrite this file to that schema, converting each scenario to a stimulus with graders and shared defaults.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrote tests/dotnet11/process-api-net11/eval.yaml to the current Vally schema (stimuli:, graders:, defaults:). Configured 6 preference-eligible stimuli and 2 dormancy contracts (total 8 stimuli). Verified with python eng/eval-quality/check_eval_quality.py, which now passes with 0 errors.


ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"])
{
KillOnParentExit = OperatingSystem.IsWindows() || OperatingSystem.IsLinux() // Auto-teardown when this parent process exits

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the example sets KillOnParentExit to IsWindows() or IsLinux() while the section just above says the property works on Windows, Linux, and Android, so on Android and any other OS the copied example silently leaves the child unprotected and risks orphan processes. It also fails your own eval, which requires a literal assignment to true. I would set KillOnParentExit to true unconditionally and let the runtime handle platform support.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Updated Example 2 to set KillOnParentExit = true unconditionally, letting the runtime handle platform support.

@shaikhsharukh

Copy link
Copy Markdown
Contributor Author

Thank you @adamsitnik and @pamod-madubashana for the detailed review and guidance!

All feedback has been addressed and pushed in commit 9b35a5df:

1. API Definitions & Documentation Updates (@adamsitnik)

  • ProcessExitStatus: Updated struct definition to public readonly record struct ProcessExitStatus(int ExitCode, bool Canceled, PosixSignal? Signal = null); (removed Success property).
  • ProcessTextOutput: Updated struct definition to public readonly record struct ProcessTextOutput(ProcessExitStatus ExitStatus, string StandardOutput, string StandardError, int ProcessId);.
  • Struct terminology: Updated section introduction to refer to return structures instead of records.
  • Timeout & Cancellation: Documented that child processes are killed on timeout or cancellation in Process.Run and Process.RunAndCaptureText.
  • ProcessStartInfo redirection: Added note reminding callers to set RedirectStandardOutput = true and RedirectStandardError = true on ProcessStartInfo before passing it to RunAndCaptureText or instance read methods.
  • Process.StartAndForget default handles: Clarified that standard handles are redirected to NUL by default if redirection is omitted.
  • KillOnParentExit: Clarified termination on fatal crash and force kill across Windows, Linux, and Android.
  • InheritedHandles: Clarified precise control over file descriptors.
  • Silent property: Removed Silent from ProcessStartInfo Properties (retained only as an argument on Process.Run[Async]).
  • Null handling in examples: Updated Example 3 to using Process process = Process.Start(startInfo)!;.

2. Examples & Evaluation Schema (@pamod-madubashana)

  • Unconditional KillOnParentExit: Example 2 now assigns KillOnParentExit = true unconditionally, letting the runtime manage platform support.
  • Vally Schema Migration: Completely rewrote tests/dotnet11/process-api-net11/eval.yaml to the Vally schema (stimuli:, graders:, defaults:).
  • Quality Gate Verification: Configured 6 preference-eligible stimuli and 2 dormancy contract stimuli (total 8 stimuli). Verified locally with python eng/eval-quality/check_eval_quality.py, which now passes with 0 errors.
  • Tuple deconstruction example: Added an explicit example demonstrating tuple destructuring with ReadAllTextAsync() (var (stdout, stderr) = await process.ReadAllTextAsync();).

Ready for re-review!

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, big thanks for your contribution @shaikhsharukh !


### Types

Before using the new convenience methods, note the following return structures:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I am not sure if we need "return" here

Suggested change
Before using the new convenience methods, note the following return structures:
Before using the new convenience methods, note the following structures:


var startInfo = new ProcessStartInfo("dotnet", ["run", "--project", "BackgroundWorker.csproj"])
{
KillOnParentExit = true // Auto-teardown when this parent process exits

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be addressed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants